Add files using upload-large-folder tool
Browse files- .venv/.gitignore +1 -0
- .venv/.lock +0 -0
- .venv/CACHEDIR.TAG +1 -0
- .venv/bin/activate +130 -0
- .venv/bin/activate.bat +71 -0
- .venv/bin/activate.csh +76 -0
- .venv/bin/activate.fish +124 -0
- .venv/bin/activate.nu +117 -0
- .venv/bin/activate.ps1 +82 -0
- .venv/bin/activate_this.py +59 -0
- .venv/bin/deactivate.bat +39 -0
- .venv/bin/f2py +10 -0
- .venv/bin/isympy +10 -0
- .venv/bin/numpy-config +10 -0
- .venv/bin/onnxruntime_test +10 -0
- .venv/bin/pydoc.bat +22 -0
- .venv/lib/python3.13/site-packages/_virtualenv.py +101 -0
- .venv/lib/python3.13/site-packages/isympy.py +342 -0
- .venv/lib/python3.13/site-packages/packaging/__init__.py +15 -0
- .venv/lib/python3.13/site-packages/packaging/_elffile.py +108 -0
- .venv/lib/python3.13/site-packages/packaging/_manylinux.py +262 -0
- .venv/lib/python3.13/site-packages/packaging/_musllinux.py +85 -0
- .venv/lib/python3.13/site-packages/packaging/_parser.py +365 -0
- .venv/lib/python3.13/site-packages/packaging/_structures.py +69 -0
- .venv/lib/python3.13/site-packages/packaging/_tokenizer.py +193 -0
- .venv/lib/python3.13/site-packages/packaging/markers.py +388 -0
- .venv/lib/python3.13/site-packages/packaging/metadata.py +978 -0
- .venv/lib/python3.13/site-packages/packaging/py.typed +0 -0
- .venv/lib/python3.13/site-packages/packaging/pylock.py +635 -0
- .venv/lib/python3.13/site-packages/packaging/requirements.py +86 -0
- .venv/lib/python3.13/site-packages/packaging/tags.py +651 -0
- .venv/lib/python3.13/site-packages/packaging/utils.py +158 -0
- .venv/lib/python3.13/site-packages/packaging/version.py +792 -0
- .venv/lib/python3.13/site-packages/sympy/printing/__init__.py +111 -0
- .venv/lib/python3.13/site-packages/sympy/printing/codeprinter.py +1039 -0
- .venv/lib/python3.13/site-packages/sympy/printing/mathematica.py +353 -0
- .venv/lib/python3.13/site-packages/sympy/printing/mathml.py +2157 -0
- .venv/lib/python3.13/site-packages/sympy/printing/rcode.py +402 -0
- .venv/lib/python3.13/site-packages/sympy/printing/tableform.py +366 -0
- .venv/pyvenv.cfg +6 -0
- .venv/share/man/man1/isympy.1 +188 -0
- README.md +21 -0
- uv.lock +0 -0
- video/video_2026-02-18T12-43-09Z/packhouse1_inline3/predictions/004345.json +5 -0
- video/video_2026-02-18T12-43-09Z/packhouse1_inline3/predictions/009824.json +5 -0
- video/video_2026-02-18T12-43-09Z/packhouse1_inline3/predictions/010756.json +5 -0
.venv/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*
|
.venv/.lock
ADDED
|
File without changes
|
.venv/CACHEDIR.TAG
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Signature: 8a477f597d28d172789f06886806bc55
|
.venv/bin/activate
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
# This file must be used with "source bin/activate" *from bash*
|
| 23 |
+
# you cannot run it directly
|
| 24 |
+
|
| 25 |
+
if ! [ -z "${SCRIPT_PATH+_}" ] ; then
|
| 26 |
+
_OLD_SCRIPT_PATH="$SCRIPT_PATH"
|
| 27 |
+
fi
|
| 28 |
+
|
| 29 |
+
# Get script path (only used if environment is relocatable).
|
| 30 |
+
if [ -n "${BASH_VERSION:+x}" ] ; then
|
| 31 |
+
SCRIPT_PATH="${BASH_SOURCE[0]}"
|
| 32 |
+
if [ "$SCRIPT_PATH" = "$0" ]; then
|
| 33 |
+
# Only bash has a reasonably robust check for source'dness.
|
| 34 |
+
echo "You must source this script: \$ source $0" >&2
|
| 35 |
+
exit 33
|
| 36 |
+
fi
|
| 37 |
+
elif [ -n "${ZSH_VERSION:+x}" ] ; then
|
| 38 |
+
SCRIPT_PATH="${(%):-%x}"
|
| 39 |
+
elif [ -n "${KSH_VERSION:+x}" ] ; then
|
| 40 |
+
SCRIPT_PATH="${.sh.file}"
|
| 41 |
+
fi
|
| 42 |
+
|
| 43 |
+
deactivate () {
|
| 44 |
+
unset -f pydoc >/dev/null 2>&1 || true
|
| 45 |
+
|
| 46 |
+
# reset old environment variables
|
| 47 |
+
# ! [ -z ${VAR+_} ] returns true if VAR is declared at all
|
| 48 |
+
if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then
|
| 49 |
+
PATH="$_OLD_VIRTUAL_PATH"
|
| 50 |
+
export PATH
|
| 51 |
+
unset _OLD_VIRTUAL_PATH
|
| 52 |
+
fi
|
| 53 |
+
if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then
|
| 54 |
+
PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME"
|
| 55 |
+
export PYTHONHOME
|
| 56 |
+
unset _OLD_VIRTUAL_PYTHONHOME
|
| 57 |
+
fi
|
| 58 |
+
|
| 59 |
+
# The hash command must be called to get it to forget past
|
| 60 |
+
# commands. Without forgetting past commands the $PATH changes
|
| 61 |
+
# we made may not be respected
|
| 62 |
+
hash -r 2>/dev/null
|
| 63 |
+
|
| 64 |
+
if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then
|
| 65 |
+
PS1="$_OLD_VIRTUAL_PS1"
|
| 66 |
+
export PS1
|
| 67 |
+
unset _OLD_VIRTUAL_PS1
|
| 68 |
+
fi
|
| 69 |
+
|
| 70 |
+
unset VIRTUAL_ENV
|
| 71 |
+
unset VIRTUAL_ENV_PROMPT
|
| 72 |
+
if [ ! "${1-}" = "nondestructive" ] ; then
|
| 73 |
+
# Self destruct!
|
| 74 |
+
unset -f deactivate
|
| 75 |
+
fi
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
# unset irrelevant variables
|
| 79 |
+
deactivate nondestructive
|
| 80 |
+
|
| 81 |
+
VIRTUAL_ENV='/private/var/folders/n5/mhkw_yp524b5wpymhzqpt5dh0000gn/T/tmpz5wzjxxz/repo/.venv'
|
| 82 |
+
if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then
|
| 83 |
+
VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV")
|
| 84 |
+
fi
|
| 85 |
+
export VIRTUAL_ENV
|
| 86 |
+
|
| 87 |
+
# Unset the `SCRIPT_PATH` variable, now that the `VIRTUAL_ENV` variable
|
| 88 |
+
# has been set. This is important for relocatable environments.
|
| 89 |
+
if ! [ -z "${_OLD_SCRIPT_PATH+_}" ] ; then
|
| 90 |
+
SCRIPT_PATH="$_OLD_SCRIPT_PATH"
|
| 91 |
+
export SCRIPT_PATH
|
| 92 |
+
unset _OLD_SCRIPT_PATH
|
| 93 |
+
else
|
| 94 |
+
unset SCRIPT_PATH
|
| 95 |
+
fi
|
| 96 |
+
|
| 97 |
+
_OLD_VIRTUAL_PATH="$PATH"
|
| 98 |
+
PATH="$VIRTUAL_ENV/bin:$PATH"
|
| 99 |
+
export PATH
|
| 100 |
+
|
| 101 |
+
if [ "xonnx-runner-detection" != x ] ; then
|
| 102 |
+
VIRTUAL_ENV_PROMPT="onnx-runner-detection"
|
| 103 |
+
else
|
| 104 |
+
VIRTUAL_ENV_PROMPT=$(basename "$VIRTUAL_ENV")
|
| 105 |
+
fi
|
| 106 |
+
export VIRTUAL_ENV_PROMPT
|
| 107 |
+
|
| 108 |
+
# unset PYTHONHOME if set
|
| 109 |
+
if ! [ -z "${PYTHONHOME+_}" ] ; then
|
| 110 |
+
_OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
|
| 111 |
+
unset PYTHONHOME
|
| 112 |
+
fi
|
| 113 |
+
|
| 114 |
+
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
|
| 115 |
+
_OLD_VIRTUAL_PS1="${PS1-}"
|
| 116 |
+
PS1="(${VIRTUAL_ENV_PROMPT}) ${PS1-}"
|
| 117 |
+
export PS1
|
| 118 |
+
fi
|
| 119 |
+
|
| 120 |
+
# Make sure to unalias pydoc if it's already there
|
| 121 |
+
alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true
|
| 122 |
+
|
| 123 |
+
pydoc () {
|
| 124 |
+
python -m pydoc "$@"
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
# The hash command must be called to get it to forget past
|
| 128 |
+
# commands. Without forgetting past commands the $PATH changes
|
| 129 |
+
# we made may not be respected
|
| 130 |
+
hash -r 2>/dev/null
|
.venv/bin/activate.bat
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@REM Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
@REM
|
| 3 |
+
@REM Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
@REM a copy of this software and associated documentation files (the
|
| 5 |
+
@REM "Software"), to deal in the Software without restriction, including
|
| 6 |
+
@REM without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
@REM distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
@REM permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
@REM the following conditions:
|
| 10 |
+
@REM
|
| 11 |
+
@REM The above copyright notice and this permission notice shall be
|
| 12 |
+
@REM included in all copies or substantial portions of the Software.
|
| 13 |
+
@REM
|
| 14 |
+
@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
@REM EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
@REM MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
@REM NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
@REM LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
@REM OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
@REM WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
@REM This file is UTF-8 encoded, so we need to update the current code page while executing it
|
| 23 |
+
@for /f "tokens=2 delims=:." %%a in ('"%SystemRoot%\System32\chcp.com"') do @set _OLD_CODEPAGE=%%a
|
| 24 |
+
@if defined _OLD_CODEPAGE (
|
| 25 |
+
@"%SystemRoot%\System32\chcp.com" 65001 > nul
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
@for %%i in ("/private/var/folders/n5/mhkw_yp524b5wpymhzqpt5dh0000gn/T/tmpz5wzjxxz/repo/.venv") do @set "VIRTUAL_ENV=%%~fi"
|
| 29 |
+
|
| 30 |
+
@set "VIRTUAL_ENV_PROMPT=onnx-runner-detection"
|
| 31 |
+
@if NOT DEFINED VIRTUAL_ENV_PROMPT (
|
| 32 |
+
@for %%d in ("%VIRTUAL_ENV%") do @set "VIRTUAL_ENV_PROMPT=%%~nxd"
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
@if defined _OLD_VIRTUAL_PROMPT (
|
| 36 |
+
@set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
|
| 37 |
+
) else (
|
| 38 |
+
@if not defined PROMPT (
|
| 39 |
+
@set "PROMPT=$P$G"
|
| 40 |
+
)
|
| 41 |
+
@if not defined VIRTUAL_ENV_DISABLE_PROMPT (
|
| 42 |
+
@set "_OLD_VIRTUAL_PROMPT=%PROMPT%"
|
| 43 |
+
)
|
| 44 |
+
)
|
| 45 |
+
@if not defined VIRTUAL_ENV_DISABLE_PROMPT (
|
| 46 |
+
@set "PROMPT=(%VIRTUAL_ENV_PROMPT%) %PROMPT%"
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
@REM Don't use () to avoid problems with them in %PATH%
|
| 50 |
+
@if defined _OLD_VIRTUAL_PYTHONHOME @goto ENDIFVHOME
|
| 51 |
+
@set "_OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%"
|
| 52 |
+
:ENDIFVHOME
|
| 53 |
+
|
| 54 |
+
@set PYTHONHOME=
|
| 55 |
+
|
| 56 |
+
@REM if defined _OLD_VIRTUAL_PATH (
|
| 57 |
+
@if not defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH1
|
| 58 |
+
@set "PATH=%_OLD_VIRTUAL_PATH%"
|
| 59 |
+
:ENDIFVPATH1
|
| 60 |
+
@REM ) else (
|
| 61 |
+
@if defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH2
|
| 62 |
+
@set "_OLD_VIRTUAL_PATH=%PATH%"
|
| 63 |
+
:ENDIFVPATH2
|
| 64 |
+
|
| 65 |
+
@set "PATH=%VIRTUAL_ENV%\bin;%PATH%"
|
| 66 |
+
|
| 67 |
+
:END
|
| 68 |
+
@if defined _OLD_CODEPAGE (
|
| 69 |
+
@"%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul
|
| 70 |
+
@set _OLD_CODEPAGE=
|
| 71 |
+
)
|
.venv/bin/activate.csh
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
# This file must be used with "source bin/activate.csh" *from csh*.
|
| 23 |
+
# You cannot run it directly.
|
| 24 |
+
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
| 25 |
+
|
| 26 |
+
set newline='\
|
| 27 |
+
'
|
| 28 |
+
|
| 29 |
+
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH:q" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT:q" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate && unalias pydoc'
|
| 30 |
+
|
| 31 |
+
# Unset irrelevant variables.
|
| 32 |
+
deactivate nondestructive
|
| 33 |
+
|
| 34 |
+
setenv VIRTUAL_ENV '/private/var/folders/n5/mhkw_yp524b5wpymhzqpt5dh0000gn/T/tmpz5wzjxxz/repo/.venv'
|
| 35 |
+
|
| 36 |
+
set _OLD_VIRTUAL_PATH="$PATH:q"
|
| 37 |
+
setenv PATH "$VIRTUAL_ENV:q/bin:$PATH:q"
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
if ('onnx-runner-detection' != "") then
|
| 42 |
+
setenv VIRTUAL_ENV_PROMPT 'onnx-runner-detection'
|
| 43 |
+
else
|
| 44 |
+
setenv VIRTUAL_ENV_PROMPT "$VIRTUAL_ENV:t:q"
|
| 45 |
+
endif
|
| 46 |
+
|
| 47 |
+
if ( $?VIRTUAL_ENV_DISABLE_PROMPT ) then
|
| 48 |
+
if ( $VIRTUAL_ENV_DISABLE_PROMPT == "" ) then
|
| 49 |
+
set do_prompt = "1"
|
| 50 |
+
else
|
| 51 |
+
set do_prompt = "0"
|
| 52 |
+
endif
|
| 53 |
+
else
|
| 54 |
+
set do_prompt = "1"
|
| 55 |
+
endif
|
| 56 |
+
|
| 57 |
+
if ( $do_prompt == "1" ) then
|
| 58 |
+
# Could be in a non-interactive environment,
|
| 59 |
+
# in which case, $prompt is undefined and we wouldn't
|
| 60 |
+
# care about the prompt anyway.
|
| 61 |
+
if ( $?prompt ) then
|
| 62 |
+
set _OLD_VIRTUAL_PROMPT="$prompt:q"
|
| 63 |
+
if ( "$prompt:q" =~ *"$newline:q"* ) then
|
| 64 |
+
:
|
| 65 |
+
else
|
| 66 |
+
set prompt = '('"$VIRTUAL_ENV_PROMPT:q"') '"$prompt:q"
|
| 67 |
+
endif
|
| 68 |
+
endif
|
| 69 |
+
endif
|
| 70 |
+
|
| 71 |
+
unset env_name
|
| 72 |
+
unset do_prompt
|
| 73 |
+
|
| 74 |
+
alias pydoc python -m pydoc
|
| 75 |
+
|
| 76 |
+
rehash
|
.venv/bin/activate.fish
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
# This file must be used using `source bin/activate.fish` *within a running fish ( http://fishshell.com ) session*.
|
| 23 |
+
# Do not run it directly.
|
| 24 |
+
|
| 25 |
+
function _bashify_path -d "Converts a fish path to something bash can recognize"
|
| 26 |
+
set fishy_path $argv
|
| 27 |
+
set bashy_path $fishy_path[1]
|
| 28 |
+
for path_part in $fishy_path[2..-1]
|
| 29 |
+
set bashy_path "$bashy_path:$path_part"
|
| 30 |
+
end
|
| 31 |
+
echo $bashy_path
|
| 32 |
+
end
|
| 33 |
+
|
| 34 |
+
function _fishify_path -d "Converts a bash path to something fish can recognize"
|
| 35 |
+
echo $argv | tr ':' '\n'
|
| 36 |
+
end
|
| 37 |
+
|
| 38 |
+
function deactivate -d 'Exit virtualenv mode and return to the normal environment.'
|
| 39 |
+
# reset old environment variables
|
| 40 |
+
if test -n "$_OLD_VIRTUAL_PATH"
|
| 41 |
+
# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
|
| 42 |
+
if test (echo $FISH_VERSION | head -c 1) -lt 3
|
| 43 |
+
set -gx PATH (_fishify_path "$_OLD_VIRTUAL_PATH")
|
| 44 |
+
else
|
| 45 |
+
set -gx PATH $_OLD_VIRTUAL_PATH
|
| 46 |
+
end
|
| 47 |
+
set -e _OLD_VIRTUAL_PATH
|
| 48 |
+
end
|
| 49 |
+
|
| 50 |
+
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
| 51 |
+
set -gx PYTHONHOME "$_OLD_VIRTUAL_PYTHONHOME"
|
| 52 |
+
set -e _OLD_VIRTUAL_PYTHONHOME
|
| 53 |
+
end
|
| 54 |
+
|
| 55 |
+
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
| 56 |
+
and functions -q _old_fish_prompt
|
| 57 |
+
# Set an empty local `$fish_function_path` to allow the removal of `fish_prompt` using `functions -e`.
|
| 58 |
+
set -l fish_function_path
|
| 59 |
+
|
| 60 |
+
# Erase virtualenv's `fish_prompt` and restore the original.
|
| 61 |
+
functions -e fish_prompt
|
| 62 |
+
functions -c _old_fish_prompt fish_prompt
|
| 63 |
+
functions -e _old_fish_prompt
|
| 64 |
+
set -e _OLD_FISH_PROMPT_OVERRIDE
|
| 65 |
+
end
|
| 66 |
+
|
| 67 |
+
set -e VIRTUAL_ENV
|
| 68 |
+
set -e VIRTUAL_ENV_PROMPT
|
| 69 |
+
|
| 70 |
+
if test "$argv[1]" != 'nondestructive'
|
| 71 |
+
# Self-destruct!
|
| 72 |
+
functions -e pydoc
|
| 73 |
+
functions -e deactivate
|
| 74 |
+
functions -e _bashify_path
|
| 75 |
+
functions -e _fishify_path
|
| 76 |
+
end
|
| 77 |
+
end
|
| 78 |
+
|
| 79 |
+
# Unset irrelevant variables.
|
| 80 |
+
deactivate nondestructive
|
| 81 |
+
|
| 82 |
+
set -gx VIRTUAL_ENV '/private/var/folders/n5/mhkw_yp524b5wpymhzqpt5dh0000gn/T/tmpz5wzjxxz/repo/.venv'
|
| 83 |
+
|
| 84 |
+
# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
|
| 85 |
+
if test (echo $FISH_VERSION | head -c 1) -lt 3
|
| 86 |
+
set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH)
|
| 87 |
+
else
|
| 88 |
+
set -gx _OLD_VIRTUAL_PATH $PATH
|
| 89 |
+
end
|
| 90 |
+
set -gx PATH "$VIRTUAL_ENV"'/bin' $PATH
|
| 91 |
+
|
| 92 |
+
# Prompt override provided?
|
| 93 |
+
# If not, just use the environment name.
|
| 94 |
+
if test -n 'onnx-runner-detection'
|
| 95 |
+
set -gx VIRTUAL_ENV_PROMPT 'onnx-runner-detection'
|
| 96 |
+
else
|
| 97 |
+
set -gx VIRTUAL_ENV_PROMPT (basename "$VIRTUAL_ENV")
|
| 98 |
+
end
|
| 99 |
+
|
| 100 |
+
# Unset `$PYTHONHOME` if set.
|
| 101 |
+
if set -q PYTHONHOME
|
| 102 |
+
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
| 103 |
+
set -e PYTHONHOME
|
| 104 |
+
end
|
| 105 |
+
|
| 106 |
+
function pydoc
|
| 107 |
+
python -m pydoc $argv
|
| 108 |
+
end
|
| 109 |
+
|
| 110 |
+
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
| 111 |
+
# Copy the current `fish_prompt` function as `_old_fish_prompt`.
|
| 112 |
+
functions -c fish_prompt _old_fish_prompt
|
| 113 |
+
|
| 114 |
+
function fish_prompt
|
| 115 |
+
# Run the user's prompt first; it might depend on (pipe)status.
|
| 116 |
+
set -l prompt (_old_fish_prompt)
|
| 117 |
+
|
| 118 |
+
printf '(%s) ' $VIRTUAL_ENV_PROMPT
|
| 119 |
+
|
| 120 |
+
string join -- \n $prompt # handle multi-line prompts
|
| 121 |
+
end
|
| 122 |
+
|
| 123 |
+
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
| 124 |
+
end
|
.venv/bin/activate.nu
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
# virtualenv activation module
|
| 23 |
+
# Activate with `overlay use activate.nu`
|
| 24 |
+
# Deactivate with `deactivate`, as usual
|
| 25 |
+
#
|
| 26 |
+
# To customize the overlay name, you can call `overlay use activate.nu as foo`,
|
| 27 |
+
# but then simply `deactivate` won't work because it is just an alias to hide
|
| 28 |
+
# the "activate" overlay. You'd need to call `overlay hide foo` manually.
|
| 29 |
+
|
| 30 |
+
export-env {
|
| 31 |
+
def is-string [x] {
|
| 32 |
+
($x | describe) == 'string'
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
def has-env [...names] {
|
| 36 |
+
$names | each {|n|
|
| 37 |
+
$n in $env
|
| 38 |
+
} | all {|i| $i == true}
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
# Emulates a `test -z`, but better as it handles e.g 'false'
|
| 42 |
+
def is-env-true [name: string] {
|
| 43 |
+
if (has-env $name) {
|
| 44 |
+
# Try to parse 'true', '0', '1', and fail if not convertible
|
| 45 |
+
let parsed = (do -i { $env | get $name | into bool })
|
| 46 |
+
if ($parsed | describe) == 'bool' {
|
| 47 |
+
$parsed
|
| 48 |
+
} else {
|
| 49 |
+
not ($env | get -i $name | is-empty)
|
| 50 |
+
}
|
| 51 |
+
} else {
|
| 52 |
+
false
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
let virtual_env = '/private/var/folders/n5/mhkw_yp524b5wpymhzqpt5dh0000gn/T/tmpz5wzjxxz/repo/.venv'
|
| 57 |
+
let bin = 'bin'
|
| 58 |
+
|
| 59 |
+
let is_windows = ($nu.os-info.family) == 'windows'
|
| 60 |
+
let path_name = (if (has-env 'Path') {
|
| 61 |
+
'Path'
|
| 62 |
+
} else {
|
| 63 |
+
'PATH'
|
| 64 |
+
}
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
let venv_path = ([$virtual_env $bin] | path join)
|
| 68 |
+
let new_path = ($env | get $path_name | prepend $venv_path)
|
| 69 |
+
|
| 70 |
+
# If there is no default prompt, then use the env name instead
|
| 71 |
+
let virtual_env_prompt = (if ('onnx-runner-detection' | is-empty) {
|
| 72 |
+
($virtual_env | path basename)
|
| 73 |
+
} else {
|
| 74 |
+
'onnx-runner-detection'
|
| 75 |
+
})
|
| 76 |
+
|
| 77 |
+
let new_env = {
|
| 78 |
+
$path_name : $new_path
|
| 79 |
+
VIRTUAL_ENV : $virtual_env
|
| 80 |
+
VIRTUAL_ENV_PROMPT : $virtual_env_prompt
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
let new_env = (if (is-env-true 'VIRTUAL_ENV_DISABLE_PROMPT') {
|
| 84 |
+
$new_env
|
| 85 |
+
} else {
|
| 86 |
+
# Creating the new prompt for the session
|
| 87 |
+
let virtual_prefix = $'(char lparen)($virtual_env_prompt)(char rparen) '
|
| 88 |
+
|
| 89 |
+
# Back up the old prompt builder
|
| 90 |
+
let old_prompt_command = (if (has-env 'PROMPT_COMMAND') {
|
| 91 |
+
$env.PROMPT_COMMAND
|
| 92 |
+
} else {
|
| 93 |
+
''
|
| 94 |
+
})
|
| 95 |
+
|
| 96 |
+
let new_prompt = (if (has-env 'PROMPT_COMMAND') {
|
| 97 |
+
if 'closure' in ($old_prompt_command | describe) {
|
| 98 |
+
{|| $'($virtual_prefix)(do $old_prompt_command)' }
|
| 99 |
+
} else {
|
| 100 |
+
{|| $'($virtual_prefix)($old_prompt_command)' }
|
| 101 |
+
}
|
| 102 |
+
} else {
|
| 103 |
+
{|| $'($virtual_prefix)' }
|
| 104 |
+
})
|
| 105 |
+
|
| 106 |
+
$new_env | merge {
|
| 107 |
+
PROMPT_COMMAND : $new_prompt
|
| 108 |
+
VIRTUAL_PREFIX : $virtual_prefix
|
| 109 |
+
}
|
| 110 |
+
})
|
| 111 |
+
|
| 112 |
+
# Environment variables that will be loaded as the virtual env
|
| 113 |
+
load-env $new_env
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
export alias pydoc = python -m pydoc
|
| 117 |
+
export alias deactivate = overlay hide activate
|
.venv/bin/activate.ps1
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
$script:THIS_PATH = $myinvocation.mycommand.path
|
| 23 |
+
$script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent
|
| 24 |
+
|
| 25 |
+
function global:deactivate([switch] $NonDestructive) {
|
| 26 |
+
if (Test-Path variable:_OLD_VIRTUAL_PATH) {
|
| 27 |
+
$env:PATH = $variable:_OLD_VIRTUAL_PATH
|
| 28 |
+
Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
if (Test-Path function:_old_virtual_prompt) {
|
| 32 |
+
$function:prompt = $function:_old_virtual_prompt
|
| 33 |
+
Remove-Item function:\_old_virtual_prompt
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
if ($env:VIRTUAL_ENV) {
|
| 37 |
+
Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
if ($env:VIRTUAL_ENV_PROMPT) {
|
| 41 |
+
Remove-Item env:VIRTUAL_ENV_PROMPT -ErrorAction SilentlyContinue
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
if (!$NonDestructive) {
|
| 45 |
+
# Self destruct!
|
| 46 |
+
Remove-Item function:deactivate
|
| 47 |
+
Remove-Item function:pydoc
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
function global:pydoc {
|
| 52 |
+
python -m pydoc $args
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
# unset irrelevant variables
|
| 56 |
+
deactivate -nondestructive
|
| 57 |
+
|
| 58 |
+
$VIRTUAL_ENV = $BASE_DIR
|
| 59 |
+
$env:VIRTUAL_ENV = $VIRTUAL_ENV
|
| 60 |
+
|
| 61 |
+
if ("onnx-runner-detection" -ne "") {
|
| 62 |
+
$env:VIRTUAL_ENV_PROMPT = "onnx-runner-detection"
|
| 63 |
+
}
|
| 64 |
+
else {
|
| 65 |
+
$env:VIRTUAL_ENV_PROMPT = $( Split-Path $env:VIRTUAL_ENV -Leaf )
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH
|
| 69 |
+
|
| 70 |
+
$env:PATH = "$env:VIRTUAL_ENV/bin:" + $env:PATH
|
| 71 |
+
if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
| 72 |
+
function global:_old_virtual_prompt {
|
| 73 |
+
""
|
| 74 |
+
}
|
| 75 |
+
$function:_old_virtual_prompt = $function:prompt
|
| 76 |
+
|
| 77 |
+
function global:prompt {
|
| 78 |
+
# Add the custom prefix to the existing prompt
|
| 79 |
+
$previous_prompt_value = & $function:_old_virtual_prompt
|
| 80 |
+
("(" + $env:VIRTUAL_ENV_PROMPT + ") " + $previous_prompt_value)
|
| 81 |
+
}
|
| 82 |
+
}
|
.venv/bin/activate_this.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
"""
|
| 23 |
+
Activate virtualenv for current interpreter:
|
| 24 |
+
|
| 25 |
+
import runpy
|
| 26 |
+
runpy.run_path(this_file)
|
| 27 |
+
|
| 28 |
+
This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.
|
| 29 |
+
""" # noqa: D415
|
| 30 |
+
|
| 31 |
+
from __future__ import annotations
|
| 32 |
+
|
| 33 |
+
import os
|
| 34 |
+
import site
|
| 35 |
+
import sys
|
| 36 |
+
|
| 37 |
+
try:
|
| 38 |
+
abs_file = os.path.abspath(__file__)
|
| 39 |
+
except NameError as exc:
|
| 40 |
+
msg = "You must use import runpy; runpy.run_path(this_file)"
|
| 41 |
+
raise AssertionError(msg) from exc
|
| 42 |
+
|
| 43 |
+
bin_dir = os.path.dirname(abs_file)
|
| 44 |
+
base = bin_dir[: -len("bin") - 1] # strip away the bin part from the __file__, plus the path separator
|
| 45 |
+
|
| 46 |
+
# prepend bin to PATH (this file is inside the bin directory)
|
| 47 |
+
os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)])
|
| 48 |
+
os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
|
| 49 |
+
os.environ["VIRTUAL_ENV_PROMPT"] = "onnx-runner-detection" or os.path.basename(base) # noqa: SIM222
|
| 50 |
+
|
| 51 |
+
# add the virtual environments libraries to the host python import mechanism
|
| 52 |
+
prev_length = len(sys.path)
|
| 53 |
+
for lib in "../lib/python3.13/site-packages".split(os.pathsep):
|
| 54 |
+
path = os.path.realpath(os.path.join(bin_dir, lib))
|
| 55 |
+
site.addsitedir(path)
|
| 56 |
+
sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
|
| 57 |
+
|
| 58 |
+
sys.real_prefix = sys.prefix
|
| 59 |
+
sys.prefix = base
|
.venv/bin/deactivate.bat
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@REM Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
@REM
|
| 3 |
+
@REM Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
@REM a copy of this software and associated documentation files (the
|
| 5 |
+
@REM "Software"), to deal in the Software without restriction, including
|
| 6 |
+
@REM without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
@REM distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
@REM permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
@REM the following conditions:
|
| 10 |
+
@REM
|
| 11 |
+
@REM The above copyright notice and this permission notice shall be
|
| 12 |
+
@REM included in all copies or substantial portions of the Software.
|
| 13 |
+
@REM
|
| 14 |
+
@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
@REM EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
@REM MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
@REM NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
@REM LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
@REM OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
@REM WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
@set VIRTUAL_ENV=
|
| 23 |
+
@set VIRTUAL_ENV_PROMPT=
|
| 24 |
+
|
| 25 |
+
@REM Don't use () to avoid problems with them in %PATH%
|
| 26 |
+
@if not defined _OLD_VIRTUAL_PROMPT @goto ENDIFVPROMPT
|
| 27 |
+
@set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
|
| 28 |
+
@set _OLD_VIRTUAL_PROMPT=
|
| 29 |
+
:ENDIFVPROMPT
|
| 30 |
+
|
| 31 |
+
@if not defined _OLD_VIRTUAL_PYTHONHOME @goto ENDIFVHOME
|
| 32 |
+
@set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%"
|
| 33 |
+
@set _OLD_VIRTUAL_PYTHONHOME=
|
| 34 |
+
:ENDIFVHOME
|
| 35 |
+
|
| 36 |
+
@if not defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH
|
| 37 |
+
@set "PATH=%_OLD_VIRTUAL_PATH%"
|
| 38 |
+
@set _OLD_VIRTUAL_PATH=
|
| 39 |
+
:ENDIFVPATH
|
.venv/bin/f2py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/private/var/folders/n5/mhkw_yp524b5wpymhzqpt5dh0000gn/T/tmpz5wzjxxz/repo/.venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from numpy.f2py.f2py2e import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
.venv/bin/isympy
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/private/var/folders/n5/mhkw_yp524b5wpymhzqpt5dh0000gn/T/tmpz5wzjxxz/repo/.venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from isympy import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
.venv/bin/numpy-config
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/private/var/folders/n5/mhkw_yp524b5wpymhzqpt5dh0000gn/T/tmpz5wzjxxz/repo/.venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from numpy._configtool import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
.venv/bin/onnxruntime_test
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/private/var/folders/n5/mhkw_yp524b5wpymhzqpt5dh0000gn/T/tmpz5wzjxxz/repo/.venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from onnxruntime.tools.onnxruntime_test import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
.venv/bin/pydoc.bat
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@REM Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
@REM
|
| 3 |
+
@REM Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
@REM a copy of this software and associated documentation files (the
|
| 5 |
+
@REM "Software"), to deal in the Software without restriction, including
|
| 6 |
+
@REM without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
@REM distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
@REM permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
@REM the following conditions:
|
| 10 |
+
@REM
|
| 11 |
+
@REM The above copyright notice and this permission notice shall be
|
| 12 |
+
@REM included in all copies or substantial portions of the Software.
|
| 13 |
+
@REM
|
| 14 |
+
@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
@REM EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
@REM MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
@REM NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
@REM LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
@REM OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
@REM WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
python.exe -m pydoc %*
|
.venv/lib/python3.13/site-packages/_virtualenv.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Patches that are applied at runtime to the virtual environment."""
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
VIRTUALENV_PATCH_FILE = os.path.join(__file__)
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def patch_dist(dist):
|
| 10 |
+
"""
|
| 11 |
+
Distutils allows user to configure some arguments via a configuration file:
|
| 12 |
+
https://docs.python.org/3.11/install/index.html#distutils-configuration-files.
|
| 13 |
+
|
| 14 |
+
Some of this arguments though don't make sense in context of the virtual environment files, let's fix them up.
|
| 15 |
+
""" # noqa: D205
|
| 16 |
+
# we cannot allow some install config as that would get packages installed outside of the virtual environment
|
| 17 |
+
old_parse_config_files = dist.Distribution.parse_config_files
|
| 18 |
+
|
| 19 |
+
def parse_config_files(self, *args, **kwargs):
|
| 20 |
+
result = old_parse_config_files(self, *args, **kwargs)
|
| 21 |
+
install = self.get_option_dict("install")
|
| 22 |
+
|
| 23 |
+
if "prefix" in install: # the prefix governs where to install the libraries
|
| 24 |
+
install["prefix"] = VIRTUALENV_PATCH_FILE, os.path.abspath(sys.prefix)
|
| 25 |
+
for base in ("purelib", "platlib", "headers", "scripts", "data"):
|
| 26 |
+
key = f"install_{base}"
|
| 27 |
+
if key in install: # do not allow global configs to hijack venv paths
|
| 28 |
+
install.pop(key, None)
|
| 29 |
+
return result
|
| 30 |
+
|
| 31 |
+
dist.Distribution.parse_config_files = parse_config_files
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# Import hook that patches some modules to ignore configuration values that break package installation in case
|
| 35 |
+
# of virtual environments.
|
| 36 |
+
_DISTUTILS_PATCH = "distutils.dist", "setuptools.dist"
|
| 37 |
+
# https://docs.python.org/3/library/importlib.html#setting-up-an-importer
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class _Finder:
|
| 41 |
+
"""A meta path finder that allows patching the imported distutils modules."""
|
| 42 |
+
|
| 43 |
+
fullname = None
|
| 44 |
+
|
| 45 |
+
# lock[0] is threading.Lock(), but initialized lazily to avoid importing threading very early at startup,
|
| 46 |
+
# because there are gevent-based applications that need to be first to import threading by themselves.
|
| 47 |
+
# See https://github.com/pypa/virtualenv/issues/1895 for details.
|
| 48 |
+
lock = [] # noqa: RUF012
|
| 49 |
+
|
| 50 |
+
def find_spec(self, fullname, path, target=None): # noqa: ARG002
|
| 51 |
+
if fullname in _DISTUTILS_PATCH and self.fullname is None:
|
| 52 |
+
# initialize lock[0] lazily
|
| 53 |
+
if len(self.lock) == 0:
|
| 54 |
+
import threading
|
| 55 |
+
|
| 56 |
+
lock = threading.Lock()
|
| 57 |
+
# there is possibility that two threads T1 and T2 are simultaneously running into find_spec,
|
| 58 |
+
# observing .lock as empty, and further going into hereby initialization. However due to the GIL,
|
| 59 |
+
# list.append() operation is atomic and this way only one of the threads will "win" to put the lock
|
| 60 |
+
# - that every thread will use - into .lock[0].
|
| 61 |
+
# https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
|
| 62 |
+
self.lock.append(lock)
|
| 63 |
+
|
| 64 |
+
from functools import partial
|
| 65 |
+
from importlib.util import find_spec
|
| 66 |
+
|
| 67 |
+
with self.lock[0]:
|
| 68 |
+
self.fullname = fullname
|
| 69 |
+
try:
|
| 70 |
+
spec = find_spec(fullname, path)
|
| 71 |
+
if spec is not None:
|
| 72 |
+
# https://www.python.org/dev/peps/pep-0451/#how-loading-will-work
|
| 73 |
+
is_new_api = hasattr(spec.loader, "exec_module")
|
| 74 |
+
func_name = "exec_module" if is_new_api else "load_module"
|
| 75 |
+
old = getattr(spec.loader, func_name)
|
| 76 |
+
func = self.exec_module if is_new_api else self.load_module
|
| 77 |
+
if old is not func:
|
| 78 |
+
try: # noqa: SIM105
|
| 79 |
+
setattr(spec.loader, func_name, partial(func, old))
|
| 80 |
+
except AttributeError:
|
| 81 |
+
pass # C-Extension loaders are r/o such as zipimporter with <3.7
|
| 82 |
+
return spec
|
| 83 |
+
finally:
|
| 84 |
+
self.fullname = None
|
| 85 |
+
return None
|
| 86 |
+
|
| 87 |
+
@staticmethod
|
| 88 |
+
def exec_module(old, module):
|
| 89 |
+
old(module)
|
| 90 |
+
if module.__name__ in _DISTUTILS_PATCH:
|
| 91 |
+
patch_dist(module)
|
| 92 |
+
|
| 93 |
+
@staticmethod
|
| 94 |
+
def load_module(old, name):
|
| 95 |
+
module = old(name)
|
| 96 |
+
if module.__name__ in _DISTUTILS_PATCH:
|
| 97 |
+
patch_dist(module)
|
| 98 |
+
return module
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
sys.meta_path.insert(0, _Finder())
|
.venv/lib/python3.13/site-packages/isympy.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Python shell for SymPy.
|
| 3 |
+
|
| 4 |
+
This is just a normal Python shell (IPython shell if you have the
|
| 5 |
+
IPython package installed), that executes the following commands for
|
| 6 |
+
the user:
|
| 7 |
+
|
| 8 |
+
>>> from __future__ import division
|
| 9 |
+
>>> from sympy import *
|
| 10 |
+
>>> x, y, z, t = symbols('x y z t')
|
| 11 |
+
>>> k, m, n = symbols('k m n', integer=True)
|
| 12 |
+
>>> f, g, h = symbols('f g h', cls=Function)
|
| 13 |
+
>>> init_printing()
|
| 14 |
+
|
| 15 |
+
So starting 'isympy' is equivalent to starting Python (or IPython) and
|
| 16 |
+
executing the above commands by hand. It is intended for easy and quick
|
| 17 |
+
experimentation with SymPy. isympy is a good way to use SymPy as an
|
| 18 |
+
interactive calculator. If you have IPython and Matplotlib installed, then
|
| 19 |
+
interactive plotting is enabled by default.
|
| 20 |
+
|
| 21 |
+
COMMAND LINE OPTIONS
|
| 22 |
+
--------------------
|
| 23 |
+
|
| 24 |
+
-c CONSOLE, --console=CONSOLE
|
| 25 |
+
|
| 26 |
+
Use the specified shell (Python or IPython) shell as the console
|
| 27 |
+
backend instead of the default one (IPython if present, Python
|
| 28 |
+
otherwise), e.g.:
|
| 29 |
+
|
| 30 |
+
$isympy -c python
|
| 31 |
+
|
| 32 |
+
CONSOLE must be one of 'ipython' or 'python'
|
| 33 |
+
|
| 34 |
+
-p PRETTY, --pretty PRETTY
|
| 35 |
+
|
| 36 |
+
Setup pretty-printing in SymPy. When pretty-printing is enabled,
|
| 37 |
+
expressions can be printed with Unicode or ASCII. The default is
|
| 38 |
+
to use pretty-printing (with Unicode if the terminal supports it).
|
| 39 |
+
When this option is 'no', expressions will not be pretty-printed
|
| 40 |
+
and ASCII will be used:
|
| 41 |
+
|
| 42 |
+
$isympy -p no
|
| 43 |
+
|
| 44 |
+
PRETTY must be one of 'unicode', 'ascii', or 'no'
|
| 45 |
+
|
| 46 |
+
-t TYPES, --types=TYPES
|
| 47 |
+
|
| 48 |
+
Setup the ground types for the polys. By default, gmpy ground types
|
| 49 |
+
are used if gmpy2 or gmpy is installed, otherwise it falls back to python
|
| 50 |
+
ground types, which are a little bit slower. You can manually
|
| 51 |
+
choose python ground types even if gmpy is installed (e.g., for
|
| 52 |
+
testing purposes):
|
| 53 |
+
|
| 54 |
+
$isympy -t python
|
| 55 |
+
|
| 56 |
+
TYPES must be one of 'gmpy', 'gmpy1' or 'python'
|
| 57 |
+
|
| 58 |
+
Note that the ground type gmpy1 is primarily intended for testing; it
|
| 59 |
+
forces the use of gmpy version 1 even if gmpy2 is available.
|
| 60 |
+
|
| 61 |
+
This is the same as setting the environment variable
|
| 62 |
+
SYMPY_GROUND_TYPES to the given ground type (e.g.,
|
| 63 |
+
SYMPY_GROUND_TYPES='gmpy')
|
| 64 |
+
|
| 65 |
+
The ground types can be determined interactively from the variable
|
| 66 |
+
sympy.polys.domains.GROUND_TYPES.
|
| 67 |
+
|
| 68 |
+
-o ORDER, --order ORDER
|
| 69 |
+
|
| 70 |
+
Setup the ordering of terms for printing. The default is lex, which
|
| 71 |
+
orders terms lexicographically (e.g., x**2 + x + 1). You can choose
|
| 72 |
+
other orderings, such as rev-lex, which will use reverse
|
| 73 |
+
lexicographic ordering (e.g., 1 + x + x**2):
|
| 74 |
+
|
| 75 |
+
$isympy -o rev-lex
|
| 76 |
+
|
| 77 |
+
ORDER must be one of 'lex', 'rev-lex', 'grlex', 'rev-grlex',
|
| 78 |
+
'grevlex', 'rev-grevlex', 'old', or 'none'.
|
| 79 |
+
|
| 80 |
+
Note that for very large expressions, ORDER='none' may speed up
|
| 81 |
+
printing considerably but the terms will have no canonical order.
|
| 82 |
+
|
| 83 |
+
-q, --quiet
|
| 84 |
+
|
| 85 |
+
Print only Python's and SymPy's versions to stdout at startup.
|
| 86 |
+
|
| 87 |
+
-d, --doctest
|
| 88 |
+
|
| 89 |
+
Use the same format that should be used for doctests. This is
|
| 90 |
+
equivalent to -c python -p no.
|
| 91 |
+
|
| 92 |
+
-C, --no-cache
|
| 93 |
+
|
| 94 |
+
Disable the caching mechanism. Disabling the cache may slow certain
|
| 95 |
+
operations down considerably. This is useful for testing the cache,
|
| 96 |
+
or for benchmarking, as the cache can result in deceptive timings.
|
| 97 |
+
|
| 98 |
+
This is equivalent to setting the environment variable
|
| 99 |
+
SYMPY_USE_CACHE to 'no'.
|
| 100 |
+
|
| 101 |
+
-a, --auto-symbols (requires at least IPython 0.11)
|
| 102 |
+
|
| 103 |
+
Automatically create missing symbols. Normally, typing a name of a
|
| 104 |
+
Symbol that has not been instantiated first would raise NameError,
|
| 105 |
+
but with this option enabled, any undefined name will be
|
| 106 |
+
automatically created as a Symbol.
|
| 107 |
+
|
| 108 |
+
Note that this is intended only for interactive, calculator style
|
| 109 |
+
usage. In a script that uses SymPy, Symbols should be instantiated
|
| 110 |
+
at the top, so that it's clear what they are.
|
| 111 |
+
|
| 112 |
+
This will not override any names that are already defined, which
|
| 113 |
+
includes the single character letters represented by the mnemonic
|
| 114 |
+
QCOSINE (see the "Gotchas and Pitfalls" document in the
|
| 115 |
+
documentation). You can delete existing names by executing "del
|
| 116 |
+
name". If a name is defined, typing "'name' in dir()" will return True.
|
| 117 |
+
|
| 118 |
+
The Symbols that are created using this have default assumptions.
|
| 119 |
+
If you want to place assumptions on symbols, you should create them
|
| 120 |
+
using symbols() or var().
|
| 121 |
+
|
| 122 |
+
Finally, this only works in the top level namespace. So, for
|
| 123 |
+
example, if you define a function in isympy with an undefined
|
| 124 |
+
Symbol, it will not work.
|
| 125 |
+
|
| 126 |
+
See also the -i and -I options.
|
| 127 |
+
|
| 128 |
+
-i, --int-to-Integer (requires at least IPython 0.11)
|
| 129 |
+
|
| 130 |
+
Automatically wrap int literals with Integer. This makes it so that
|
| 131 |
+
things like 1/2 will come out as Rational(1, 2), rather than 0.5. This
|
| 132 |
+
works by preprocessing the source and wrapping all int literals with
|
| 133 |
+
Integer. Note that this will not change the behavior of int literals
|
| 134 |
+
assigned to variables, and it also won't change the behavior of functions
|
| 135 |
+
that return int literals.
|
| 136 |
+
|
| 137 |
+
If you want an int, you can wrap the literal in int(), e.g. int(3)/int(2)
|
| 138 |
+
gives 1.5 (with division imported from __future__).
|
| 139 |
+
|
| 140 |
+
-I, --interactive (requires at least IPython 0.11)
|
| 141 |
+
|
| 142 |
+
This is equivalent to --auto-symbols --int-to-Integer. Future options
|
| 143 |
+
designed for ease of interactive use may be added to this.
|
| 144 |
+
|
| 145 |
+
-D, --debug
|
| 146 |
+
|
| 147 |
+
Enable debugging output. This is the same as setting the
|
| 148 |
+
environment variable SYMPY_DEBUG to 'True'. The debug status is set
|
| 149 |
+
in the variable SYMPY_DEBUG within isympy.
|
| 150 |
+
|
| 151 |
+
-- IPython options
|
| 152 |
+
|
| 153 |
+
Additionally you can pass command line options directly to the IPython
|
| 154 |
+
interpreter (the standard Python shell is not supported). However you
|
| 155 |
+
need to add the '--' separator between two types of options, e.g the
|
| 156 |
+
startup banner option and the colors option. You need to enter the
|
| 157 |
+
options as required by the version of IPython that you are using, too:
|
| 158 |
+
|
| 159 |
+
in IPython 0.11,
|
| 160 |
+
|
| 161 |
+
$isympy -q -- --colors=NoColor
|
| 162 |
+
|
| 163 |
+
or older versions of IPython,
|
| 164 |
+
|
| 165 |
+
$isympy -q -- -colors NoColor
|
| 166 |
+
|
| 167 |
+
See also isympy --help.
|
| 168 |
+
"""
|
| 169 |
+
|
| 170 |
+
import os
|
| 171 |
+
import sys
|
| 172 |
+
|
| 173 |
+
# DO NOT IMPORT SYMPY HERE! Or the setting of the sympy environment variables
|
| 174 |
+
# by the command line will break.
|
| 175 |
+
|
| 176 |
+
def main() -> None:
|
| 177 |
+
from argparse import ArgumentParser, RawDescriptionHelpFormatter
|
| 178 |
+
|
| 179 |
+
VERSION = None
|
| 180 |
+
if '--version' in sys.argv:
|
| 181 |
+
# We cannot import sympy before this is run, because flags like -C and
|
| 182 |
+
# -t set environment variables that must be set before SymPy is
|
| 183 |
+
# imported. The only thing we need to import it for is to get the
|
| 184 |
+
# version, which only matters with the --version flag.
|
| 185 |
+
import sympy
|
| 186 |
+
VERSION = sympy.__version__
|
| 187 |
+
|
| 188 |
+
usage = 'isympy [options] -- [ipython options]'
|
| 189 |
+
parser = ArgumentParser(
|
| 190 |
+
usage=usage,
|
| 191 |
+
description=__doc__,
|
| 192 |
+
formatter_class=RawDescriptionHelpFormatter,
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
parser.add_argument('--version', action='version', version=VERSION)
|
| 196 |
+
|
| 197 |
+
parser.add_argument(
|
| 198 |
+
'-c', '--console',
|
| 199 |
+
dest='console',
|
| 200 |
+
action='store',
|
| 201 |
+
default=None,
|
| 202 |
+
choices=['ipython', 'python'],
|
| 203 |
+
metavar='CONSOLE',
|
| 204 |
+
help='select type of interactive session: ipython | python; defaults '
|
| 205 |
+
'to ipython if IPython is installed, otherwise python')
|
| 206 |
+
|
| 207 |
+
parser.add_argument(
|
| 208 |
+
'-p', '--pretty',
|
| 209 |
+
dest='pretty',
|
| 210 |
+
action='store',
|
| 211 |
+
default=None,
|
| 212 |
+
metavar='PRETTY',
|
| 213 |
+
choices=['unicode', 'ascii', 'no'],
|
| 214 |
+
help='setup pretty printing: unicode | ascii | no; defaults to '
|
| 215 |
+
'unicode printing if the terminal supports it, otherwise ascii')
|
| 216 |
+
|
| 217 |
+
parser.add_argument(
|
| 218 |
+
'-t', '--types',
|
| 219 |
+
dest='types',
|
| 220 |
+
action='store',
|
| 221 |
+
default=None,
|
| 222 |
+
metavar='TYPES',
|
| 223 |
+
choices=['gmpy', 'gmpy1', 'python'],
|
| 224 |
+
help='setup ground types: gmpy | gmpy1 | python; defaults to gmpy if gmpy2 '
|
| 225 |
+
'or gmpy is installed, otherwise python')
|
| 226 |
+
|
| 227 |
+
parser.add_argument(
|
| 228 |
+
'-o', '--order',
|
| 229 |
+
dest='order',
|
| 230 |
+
action='store',
|
| 231 |
+
default=None,
|
| 232 |
+
metavar='ORDER',
|
| 233 |
+
choices=['lex', 'grlex', 'grevlex', 'rev-lex', 'rev-grlex', 'rev-grevlex', 'old', 'none'],
|
| 234 |
+
help='setup ordering of terms: [rev-]lex | [rev-]grlex | [rev-]grevlex | old | none; defaults to lex')
|
| 235 |
+
|
| 236 |
+
parser.add_argument(
|
| 237 |
+
'-q', '--quiet',
|
| 238 |
+
dest='quiet',
|
| 239 |
+
action='store_true',
|
| 240 |
+
default=False,
|
| 241 |
+
help='print only version information at startup')
|
| 242 |
+
|
| 243 |
+
parser.add_argument(
|
| 244 |
+
'-d', '--doctest',
|
| 245 |
+
dest='doctest',
|
| 246 |
+
action='store_true',
|
| 247 |
+
default=False,
|
| 248 |
+
help='use the doctest format for output (you can just copy and paste it)')
|
| 249 |
+
|
| 250 |
+
parser.add_argument(
|
| 251 |
+
'-C', '--no-cache',
|
| 252 |
+
dest='cache',
|
| 253 |
+
action='store_false',
|
| 254 |
+
default=True,
|
| 255 |
+
help='disable caching mechanism')
|
| 256 |
+
|
| 257 |
+
parser.add_argument(
|
| 258 |
+
'-a', '--auto-symbols',
|
| 259 |
+
dest='auto_symbols',
|
| 260 |
+
action='store_true',
|
| 261 |
+
default=False,
|
| 262 |
+
help='automatically construct missing symbols')
|
| 263 |
+
|
| 264 |
+
parser.add_argument(
|
| 265 |
+
'-i', '--int-to-Integer',
|
| 266 |
+
dest='auto_int_to_Integer',
|
| 267 |
+
action='store_true',
|
| 268 |
+
default=False,
|
| 269 |
+
help="automatically wrap int literals with Integer")
|
| 270 |
+
|
| 271 |
+
parser.add_argument(
|
| 272 |
+
'-I', '--interactive',
|
| 273 |
+
dest='interactive',
|
| 274 |
+
action='store_true',
|
| 275 |
+
default=False,
|
| 276 |
+
help="equivalent to -a -i")
|
| 277 |
+
|
| 278 |
+
parser.add_argument(
|
| 279 |
+
'-D', '--debug',
|
| 280 |
+
dest='debug',
|
| 281 |
+
action='store_true',
|
| 282 |
+
default=False,
|
| 283 |
+
help='enable debugging output')
|
| 284 |
+
|
| 285 |
+
(options, ipy_args) = parser.parse_known_args()
|
| 286 |
+
if '--' in ipy_args:
|
| 287 |
+
ipy_args.remove('--')
|
| 288 |
+
|
| 289 |
+
if not options.cache:
|
| 290 |
+
os.environ['SYMPY_USE_CACHE'] = 'no'
|
| 291 |
+
|
| 292 |
+
if options.types:
|
| 293 |
+
os.environ['SYMPY_GROUND_TYPES'] = options.types
|
| 294 |
+
|
| 295 |
+
if options.debug:
|
| 296 |
+
os.environ['SYMPY_DEBUG'] = str(options.debug)
|
| 297 |
+
|
| 298 |
+
if options.doctest:
|
| 299 |
+
options.pretty = 'no'
|
| 300 |
+
options.console = 'python'
|
| 301 |
+
|
| 302 |
+
session = options.console
|
| 303 |
+
|
| 304 |
+
if session is not None:
|
| 305 |
+
ipython = session == 'ipython'
|
| 306 |
+
else:
|
| 307 |
+
try:
|
| 308 |
+
import IPython # noqa: F401
|
| 309 |
+
ipython = True
|
| 310 |
+
except ImportError:
|
| 311 |
+
if not options.quiet:
|
| 312 |
+
from sympy.interactive.session import no_ipython
|
| 313 |
+
print(no_ipython)
|
| 314 |
+
ipython = False
|
| 315 |
+
|
| 316 |
+
args = {
|
| 317 |
+
'pretty_print': True,
|
| 318 |
+
'use_unicode': None,
|
| 319 |
+
'use_latex': None,
|
| 320 |
+
'order': None,
|
| 321 |
+
'argv': ipy_args,
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
if options.pretty == 'unicode':
|
| 325 |
+
args['use_unicode'] = True
|
| 326 |
+
elif options.pretty == 'ascii':
|
| 327 |
+
args['use_unicode'] = False
|
| 328 |
+
elif options.pretty == 'no':
|
| 329 |
+
args['pretty_print'] = False
|
| 330 |
+
|
| 331 |
+
if options.order is not None:
|
| 332 |
+
args['order'] = options.order
|
| 333 |
+
|
| 334 |
+
args['quiet'] = options.quiet
|
| 335 |
+
args['auto_symbols'] = options.auto_symbols or options.interactive
|
| 336 |
+
args['auto_int_to_Integer'] = options.auto_int_to_Integer or options.interactive
|
| 337 |
+
|
| 338 |
+
from sympy.interactive import init_session
|
| 339 |
+
init_session(ipython, **args)
|
| 340 |
+
|
| 341 |
+
if __name__ == "__main__":
|
| 342 |
+
main()
|
.venv/lib/python3.13/site-packages/packaging/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
|
| 5 |
+
__title__ = "packaging"
|
| 6 |
+
__summary__ = "Core utilities for Python packages"
|
| 7 |
+
__uri__ = "https://github.com/pypa/packaging"
|
| 8 |
+
|
| 9 |
+
__version__ = "26.0"
|
| 10 |
+
|
| 11 |
+
__author__ = "Donald Stufft and individual contributors"
|
| 12 |
+
__email__ = "donald@stufft.io"
|
| 13 |
+
|
| 14 |
+
__license__ = "BSD-2-Clause or Apache-2.0"
|
| 15 |
+
__copyright__ = f"2014 {__author__}"
|
.venv/lib/python3.13/site-packages/packaging/_elffile.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
ELF file parser.
|
| 3 |
+
|
| 4 |
+
This provides a class ``ELFFile`` that parses an ELF executable in a similar
|
| 5 |
+
interface to ``ZipFile``. Only the read interface is implemented.
|
| 6 |
+
|
| 7 |
+
ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import enum
|
| 13 |
+
import os
|
| 14 |
+
import struct
|
| 15 |
+
from typing import IO
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class ELFInvalid(ValueError):
|
| 19 |
+
pass
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class EIClass(enum.IntEnum):
|
| 23 |
+
C32 = 1
|
| 24 |
+
C64 = 2
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class EIData(enum.IntEnum):
|
| 28 |
+
Lsb = 1
|
| 29 |
+
Msb = 2
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class EMachine(enum.IntEnum):
|
| 33 |
+
I386 = 3
|
| 34 |
+
S390 = 22
|
| 35 |
+
Arm = 40
|
| 36 |
+
X8664 = 62
|
| 37 |
+
AArc64 = 183
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class ELFFile:
|
| 41 |
+
"""
|
| 42 |
+
Representation of an ELF executable.
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
def __init__(self, f: IO[bytes]) -> None:
|
| 46 |
+
self._f = f
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
ident = self._read("16B")
|
| 50 |
+
except struct.error as e:
|
| 51 |
+
raise ELFInvalid("unable to parse identification") from e
|
| 52 |
+
magic = bytes(ident[:4])
|
| 53 |
+
if magic != b"\x7fELF":
|
| 54 |
+
raise ELFInvalid(f"invalid magic: {magic!r}")
|
| 55 |
+
|
| 56 |
+
self.capacity = ident[4] # Format for program header (bitness).
|
| 57 |
+
self.encoding = ident[5] # Data structure encoding (endianness).
|
| 58 |
+
|
| 59 |
+
try:
|
| 60 |
+
# e_fmt: Format for program header.
|
| 61 |
+
# p_fmt: Format for section header.
|
| 62 |
+
# p_idx: Indexes to find p_type, p_offset, and p_filesz.
|
| 63 |
+
e_fmt, self._p_fmt, self._p_idx = {
|
| 64 |
+
(1, 1): ("<HHIIIIIHHH", "<IIIIIIII", (0, 1, 4)), # 32-bit LSB.
|
| 65 |
+
(1, 2): (">HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB.
|
| 66 |
+
(2, 1): ("<HHIQQQIHHH", "<IIQQQQQQ", (0, 2, 5)), # 64-bit LSB.
|
| 67 |
+
(2, 2): (">HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB.
|
| 68 |
+
}[(self.capacity, self.encoding)]
|
| 69 |
+
except KeyError as e:
|
| 70 |
+
raise ELFInvalid(
|
| 71 |
+
f"unrecognized capacity ({self.capacity}) or encoding ({self.encoding})"
|
| 72 |
+
) from e
|
| 73 |
+
|
| 74 |
+
try:
|
| 75 |
+
(
|
| 76 |
+
_,
|
| 77 |
+
self.machine, # Architecture type.
|
| 78 |
+
_,
|
| 79 |
+
_,
|
| 80 |
+
self._e_phoff, # Offset of program header.
|
| 81 |
+
_,
|
| 82 |
+
self.flags, # Processor-specific flags.
|
| 83 |
+
_,
|
| 84 |
+
self._e_phentsize, # Size of section.
|
| 85 |
+
self._e_phnum, # Number of sections.
|
| 86 |
+
) = self._read(e_fmt)
|
| 87 |
+
except struct.error as e:
|
| 88 |
+
raise ELFInvalid("unable to parse machine and section information") from e
|
| 89 |
+
|
| 90 |
+
def _read(self, fmt: str) -> tuple[int, ...]:
|
| 91 |
+
return struct.unpack(fmt, self._f.read(struct.calcsize(fmt)))
|
| 92 |
+
|
| 93 |
+
@property
|
| 94 |
+
def interpreter(self) -> str | None:
|
| 95 |
+
"""
|
| 96 |
+
The path recorded in the ``PT_INTERP`` section header.
|
| 97 |
+
"""
|
| 98 |
+
for index in range(self._e_phnum):
|
| 99 |
+
self._f.seek(self._e_phoff + self._e_phentsize * index)
|
| 100 |
+
try:
|
| 101 |
+
data = self._read(self._p_fmt)
|
| 102 |
+
except struct.error:
|
| 103 |
+
continue
|
| 104 |
+
if data[self._p_idx[0]] != 3: # Not PT_INTERP.
|
| 105 |
+
continue
|
| 106 |
+
self._f.seek(data[self._p_idx[1]])
|
| 107 |
+
return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0")
|
| 108 |
+
return None
|
.venv/lib/python3.13/site-packages/packaging/_manylinux.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import collections
|
| 4 |
+
import contextlib
|
| 5 |
+
import functools
|
| 6 |
+
import os
|
| 7 |
+
import re
|
| 8 |
+
import sys
|
| 9 |
+
import warnings
|
| 10 |
+
from typing import Generator, Iterator, NamedTuple, Sequence
|
| 11 |
+
|
| 12 |
+
from ._elffile import EIClass, EIData, ELFFile, EMachine
|
| 13 |
+
|
| 14 |
+
EF_ARM_ABIMASK = 0xFF000000
|
| 15 |
+
EF_ARM_ABI_VER5 = 0x05000000
|
| 16 |
+
EF_ARM_ABI_FLOAT_HARD = 0x00000400
|
| 17 |
+
|
| 18 |
+
_ALLOWED_ARCHS = {
|
| 19 |
+
"x86_64",
|
| 20 |
+
"aarch64",
|
| 21 |
+
"ppc64",
|
| 22 |
+
"ppc64le",
|
| 23 |
+
"s390x",
|
| 24 |
+
"loongarch64",
|
| 25 |
+
"riscv64",
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
|
| 30 |
+
# as the type for `path` until then.
|
| 31 |
+
@contextlib.contextmanager
|
| 32 |
+
def _parse_elf(path: str) -> Generator[ELFFile | None, None, None]:
|
| 33 |
+
try:
|
| 34 |
+
with open(path, "rb") as f:
|
| 35 |
+
yield ELFFile(f)
|
| 36 |
+
except (OSError, TypeError, ValueError):
|
| 37 |
+
yield None
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _is_linux_armhf(executable: str) -> bool:
|
| 41 |
+
# hard-float ABI can be detected from the ELF header of the running
|
| 42 |
+
# process
|
| 43 |
+
# https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
|
| 44 |
+
with _parse_elf(executable) as f:
|
| 45 |
+
return (
|
| 46 |
+
f is not None
|
| 47 |
+
and f.capacity == EIClass.C32
|
| 48 |
+
and f.encoding == EIData.Lsb
|
| 49 |
+
and f.machine == EMachine.Arm
|
| 50 |
+
and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
|
| 51 |
+
and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def _is_linux_i686(executable: str) -> bool:
|
| 56 |
+
with _parse_elf(executable) as f:
|
| 57 |
+
return (
|
| 58 |
+
f is not None
|
| 59 |
+
and f.capacity == EIClass.C32
|
| 60 |
+
and f.encoding == EIData.Lsb
|
| 61 |
+
and f.machine == EMachine.I386
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
|
| 66 |
+
if "armv7l" in archs:
|
| 67 |
+
return _is_linux_armhf(executable)
|
| 68 |
+
if "i686" in archs:
|
| 69 |
+
return _is_linux_i686(executable)
|
| 70 |
+
return any(arch in _ALLOWED_ARCHS for arch in archs)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# If glibc ever changes its major version, we need to know what the last
|
| 74 |
+
# minor version was, so we can build the complete list of all versions.
|
| 75 |
+
# For now, guess what the highest minor version might be, assume it will
|
| 76 |
+
# be 50 for testing. Once this actually happens, update the dictionary
|
| 77 |
+
# with the actual value.
|
| 78 |
+
_LAST_GLIBC_MINOR: dict[int, int] = collections.defaultdict(lambda: 50)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class _GLibCVersion(NamedTuple):
|
| 82 |
+
major: int
|
| 83 |
+
minor: int
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _glibc_version_string_confstr() -> str | None:
|
| 87 |
+
"""
|
| 88 |
+
Primary implementation of glibc_version_string using os.confstr.
|
| 89 |
+
"""
|
| 90 |
+
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
|
| 91 |
+
# to be broken or missing. This strategy is used in the standard library
|
| 92 |
+
# platform module.
|
| 93 |
+
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
|
| 94 |
+
try:
|
| 95 |
+
# Should be a string like "glibc 2.17".
|
| 96 |
+
version_string: str | None = os.confstr("CS_GNU_LIBC_VERSION")
|
| 97 |
+
assert version_string is not None
|
| 98 |
+
_, version = version_string.rsplit()
|
| 99 |
+
except (AssertionError, AttributeError, OSError, ValueError):
|
| 100 |
+
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
|
| 101 |
+
return None
|
| 102 |
+
return version
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def _glibc_version_string_ctypes() -> str | None:
|
| 106 |
+
"""
|
| 107 |
+
Fallback implementation of glibc_version_string using ctypes.
|
| 108 |
+
"""
|
| 109 |
+
try:
|
| 110 |
+
import ctypes # noqa: PLC0415
|
| 111 |
+
except ImportError:
|
| 112 |
+
return None
|
| 113 |
+
|
| 114 |
+
# ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
|
| 115 |
+
# manpage says, "If filename is NULL, then the returned handle is for the
|
| 116 |
+
# main program". This way we can let the linker do the work to figure out
|
| 117 |
+
# which libc our process is actually using.
|
| 118 |
+
#
|
| 119 |
+
# We must also handle the special case where the executable is not a
|
| 120 |
+
# dynamically linked executable. This can occur when using musl libc,
|
| 121 |
+
# for example. In this situation, dlopen() will error, leading to an
|
| 122 |
+
# OSError. Interestingly, at least in the case of musl, there is no
|
| 123 |
+
# errno set on the OSError. The single string argument used to construct
|
| 124 |
+
# OSError comes from libc itself and is therefore not portable to
|
| 125 |
+
# hard code here. In any case, failure to call dlopen() means we
|
| 126 |
+
# can proceed, so we bail on our attempt.
|
| 127 |
+
try:
|
| 128 |
+
process_namespace = ctypes.CDLL(None)
|
| 129 |
+
except OSError:
|
| 130 |
+
return None
|
| 131 |
+
|
| 132 |
+
try:
|
| 133 |
+
gnu_get_libc_version = process_namespace.gnu_get_libc_version
|
| 134 |
+
except AttributeError:
|
| 135 |
+
# Symbol doesn't exist -> therefore, we are not linked to
|
| 136 |
+
# glibc.
|
| 137 |
+
return None
|
| 138 |
+
|
| 139 |
+
# Call gnu_get_libc_version, which returns a string like "2.5"
|
| 140 |
+
gnu_get_libc_version.restype = ctypes.c_char_p
|
| 141 |
+
version_str: str = gnu_get_libc_version()
|
| 142 |
+
# py2 / py3 compatibility:
|
| 143 |
+
if not isinstance(version_str, str):
|
| 144 |
+
version_str = version_str.decode("ascii")
|
| 145 |
+
|
| 146 |
+
return version_str
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def _glibc_version_string() -> str | None:
|
| 150 |
+
"""Returns glibc version string, or None if not using glibc."""
|
| 151 |
+
return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def _parse_glibc_version(version_str: str) -> _GLibCVersion:
|
| 155 |
+
"""Parse glibc version.
|
| 156 |
+
|
| 157 |
+
We use a regexp instead of str.split because we want to discard any
|
| 158 |
+
random junk that might come after the minor version -- this might happen
|
| 159 |
+
in patched/forked versions of glibc (e.g. Linaro's version of glibc
|
| 160 |
+
uses version strings like "2.20-2014.11"). See gh-3588.
|
| 161 |
+
"""
|
| 162 |
+
m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
|
| 163 |
+
if not m:
|
| 164 |
+
warnings.warn(
|
| 165 |
+
f"Expected glibc version with 2 components major.minor, got: {version_str}",
|
| 166 |
+
RuntimeWarning,
|
| 167 |
+
stacklevel=2,
|
| 168 |
+
)
|
| 169 |
+
return _GLibCVersion(-1, -1)
|
| 170 |
+
return _GLibCVersion(int(m.group("major")), int(m.group("minor")))
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
@functools.lru_cache
|
| 174 |
+
def _get_glibc_version() -> _GLibCVersion:
|
| 175 |
+
version_str = _glibc_version_string()
|
| 176 |
+
if version_str is None:
|
| 177 |
+
return _GLibCVersion(-1, -1)
|
| 178 |
+
return _parse_glibc_version(version_str)
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
# From PEP 513, PEP 600
|
| 182 |
+
def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
|
| 183 |
+
sys_glibc = _get_glibc_version()
|
| 184 |
+
if sys_glibc < version:
|
| 185 |
+
return False
|
| 186 |
+
# Check for presence of _manylinux module.
|
| 187 |
+
try:
|
| 188 |
+
import _manylinux # noqa: PLC0415
|
| 189 |
+
except ImportError:
|
| 190 |
+
return True
|
| 191 |
+
if hasattr(_manylinux, "manylinux_compatible"):
|
| 192 |
+
result = _manylinux.manylinux_compatible(version[0], version[1], arch)
|
| 193 |
+
if result is not None:
|
| 194 |
+
return bool(result)
|
| 195 |
+
return True
|
| 196 |
+
if version == _GLibCVersion(2, 5) and hasattr(_manylinux, "manylinux1_compatible"):
|
| 197 |
+
return bool(_manylinux.manylinux1_compatible)
|
| 198 |
+
if version == _GLibCVersion(2, 12) and hasattr(
|
| 199 |
+
_manylinux, "manylinux2010_compatible"
|
| 200 |
+
):
|
| 201 |
+
return bool(_manylinux.manylinux2010_compatible)
|
| 202 |
+
if version == _GLibCVersion(2, 17) and hasattr(
|
| 203 |
+
_manylinux, "manylinux2014_compatible"
|
| 204 |
+
):
|
| 205 |
+
return bool(_manylinux.manylinux2014_compatible)
|
| 206 |
+
return True
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
_LEGACY_MANYLINUX_MAP: dict[_GLibCVersion, str] = {
|
| 210 |
+
# CentOS 7 w/ glibc 2.17 (PEP 599)
|
| 211 |
+
_GLibCVersion(2, 17): "manylinux2014",
|
| 212 |
+
# CentOS 6 w/ glibc 2.12 (PEP 571)
|
| 213 |
+
_GLibCVersion(2, 12): "manylinux2010",
|
| 214 |
+
# CentOS 5 w/ glibc 2.5 (PEP 513)
|
| 215 |
+
_GLibCVersion(2, 5): "manylinux1",
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def platform_tags(archs: Sequence[str]) -> Iterator[str]:
|
| 220 |
+
"""Generate manylinux tags compatible to the current platform.
|
| 221 |
+
|
| 222 |
+
:param archs: Sequence of compatible architectures.
|
| 223 |
+
The first one shall be the closest to the actual architecture and be the part of
|
| 224 |
+
platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
|
| 225 |
+
The ``linux_`` prefix is assumed as a prerequisite for the current platform to
|
| 226 |
+
be manylinux-compatible.
|
| 227 |
+
|
| 228 |
+
:returns: An iterator of compatible manylinux tags.
|
| 229 |
+
"""
|
| 230 |
+
if not _have_compatible_abi(sys.executable, archs):
|
| 231 |
+
return
|
| 232 |
+
# Oldest glibc to be supported regardless of architecture is (2, 17).
|
| 233 |
+
too_old_glibc2 = _GLibCVersion(2, 16)
|
| 234 |
+
if set(archs) & {"x86_64", "i686"}:
|
| 235 |
+
# On x86/i686 also oldest glibc to be supported is (2, 5).
|
| 236 |
+
too_old_glibc2 = _GLibCVersion(2, 4)
|
| 237 |
+
current_glibc = _GLibCVersion(*_get_glibc_version())
|
| 238 |
+
glibc_max_list = [current_glibc]
|
| 239 |
+
# We can assume compatibility across glibc major versions.
|
| 240 |
+
# https://sourceware.org/bugzilla/show_bug.cgi?id=24636
|
| 241 |
+
#
|
| 242 |
+
# Build a list of maximum glibc versions so that we can
|
| 243 |
+
# output the canonical list of all glibc from current_glibc
|
| 244 |
+
# down to too_old_glibc2, including all intermediary versions.
|
| 245 |
+
for glibc_major in range(current_glibc.major - 1, 1, -1):
|
| 246 |
+
glibc_minor = _LAST_GLIBC_MINOR[glibc_major]
|
| 247 |
+
glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor))
|
| 248 |
+
for arch in archs:
|
| 249 |
+
for glibc_max in glibc_max_list:
|
| 250 |
+
if glibc_max.major == too_old_glibc2.major:
|
| 251 |
+
min_minor = too_old_glibc2.minor
|
| 252 |
+
else:
|
| 253 |
+
# For other glibc major versions oldest supported is (x, 0).
|
| 254 |
+
min_minor = -1
|
| 255 |
+
for glibc_minor in range(glibc_max.minor, min_minor, -1):
|
| 256 |
+
glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
|
| 257 |
+
if _is_compatible(arch, glibc_version):
|
| 258 |
+
yield "manylinux_{}_{}_{}".format(*glibc_version, arch)
|
| 259 |
+
|
| 260 |
+
# Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
|
| 261 |
+
if legacy_tag := _LEGACY_MANYLINUX_MAP.get(glibc_version):
|
| 262 |
+
yield f"{legacy_tag}_{arch}"
|
.venv/lib/python3.13/site-packages/packaging/_musllinux.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""PEP 656 support.
|
| 2 |
+
|
| 3 |
+
This module implements logic to detect if the currently running Python is
|
| 4 |
+
linked against musl, and what musl version is used.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import functools
|
| 10 |
+
import re
|
| 11 |
+
import subprocess
|
| 12 |
+
import sys
|
| 13 |
+
from typing import Iterator, NamedTuple, Sequence
|
| 14 |
+
|
| 15 |
+
from ._elffile import ELFFile
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class _MuslVersion(NamedTuple):
|
| 19 |
+
major: int
|
| 20 |
+
minor: int
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _parse_musl_version(output: str) -> _MuslVersion | None:
|
| 24 |
+
lines = [n for n in (n.strip() for n in output.splitlines()) if n]
|
| 25 |
+
if len(lines) < 2 or lines[0][:4] != "musl":
|
| 26 |
+
return None
|
| 27 |
+
m = re.match(r"Version (\d+)\.(\d+)", lines[1])
|
| 28 |
+
if not m:
|
| 29 |
+
return None
|
| 30 |
+
return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@functools.lru_cache
|
| 34 |
+
def _get_musl_version(executable: str) -> _MuslVersion | None:
|
| 35 |
+
"""Detect currently-running musl runtime version.
|
| 36 |
+
|
| 37 |
+
This is done by checking the specified executable's dynamic linking
|
| 38 |
+
information, and invoking the loader to parse its output for a version
|
| 39 |
+
string. If the loader is musl, the output would be something like::
|
| 40 |
+
|
| 41 |
+
musl libc (x86_64)
|
| 42 |
+
Version 1.2.2
|
| 43 |
+
Dynamic Program Loader
|
| 44 |
+
"""
|
| 45 |
+
try:
|
| 46 |
+
with open(executable, "rb") as f:
|
| 47 |
+
ld = ELFFile(f).interpreter
|
| 48 |
+
except (OSError, TypeError, ValueError):
|
| 49 |
+
return None
|
| 50 |
+
if ld is None or "musl" not in ld:
|
| 51 |
+
return None
|
| 52 |
+
proc = subprocess.run([ld], check=False, stderr=subprocess.PIPE, text=True)
|
| 53 |
+
return _parse_musl_version(proc.stderr)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def platform_tags(archs: Sequence[str]) -> Iterator[str]:
|
| 57 |
+
"""Generate musllinux tags compatible to the current platform.
|
| 58 |
+
|
| 59 |
+
:param archs: Sequence of compatible architectures.
|
| 60 |
+
The first one shall be the closest to the actual architecture and be the part of
|
| 61 |
+
platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
|
| 62 |
+
The ``linux_`` prefix is assumed as a prerequisite for the current platform to
|
| 63 |
+
be musllinux-compatible.
|
| 64 |
+
|
| 65 |
+
:returns: An iterator of compatible musllinux tags.
|
| 66 |
+
"""
|
| 67 |
+
sys_musl = _get_musl_version(sys.executable)
|
| 68 |
+
if sys_musl is None: # Python not dynamically linked against musl.
|
| 69 |
+
return
|
| 70 |
+
for arch in archs:
|
| 71 |
+
for minor in range(sys_musl.minor, -1, -1):
|
| 72 |
+
yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__": # pragma: no cover
|
| 76 |
+
import sysconfig
|
| 77 |
+
|
| 78 |
+
plat = sysconfig.get_platform()
|
| 79 |
+
assert plat.startswith("linux-"), "not linux"
|
| 80 |
+
|
| 81 |
+
print("plat:", plat)
|
| 82 |
+
print("musl:", _get_musl_version(sys.executable))
|
| 83 |
+
print("tags:", end=" ")
|
| 84 |
+
for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):
|
| 85 |
+
print(t, end="\n ")
|
.venv/lib/python3.13/site-packages/packaging/_parser.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Handwritten parser of dependency specifiers.
|
| 2 |
+
|
| 3 |
+
The docstring for each __parse_* function contains EBNF-inspired grammar representing
|
| 4 |
+
the implementation.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import ast
|
| 10 |
+
from typing import List, Literal, NamedTuple, Sequence, Tuple, Union
|
| 11 |
+
|
| 12 |
+
from ._tokenizer import DEFAULT_RULES, Tokenizer
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class Node:
|
| 16 |
+
__slots__ = ("value",)
|
| 17 |
+
|
| 18 |
+
def __init__(self, value: str) -> None:
|
| 19 |
+
self.value = value
|
| 20 |
+
|
| 21 |
+
def __str__(self) -> str:
|
| 22 |
+
return self.value
|
| 23 |
+
|
| 24 |
+
def __repr__(self) -> str:
|
| 25 |
+
return f"<{self.__class__.__name__}({self.value!r})>"
|
| 26 |
+
|
| 27 |
+
def serialize(self) -> str:
|
| 28 |
+
raise NotImplementedError
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class Variable(Node):
|
| 32 |
+
__slots__ = ()
|
| 33 |
+
|
| 34 |
+
def serialize(self) -> str:
|
| 35 |
+
return str(self)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class Value(Node):
|
| 39 |
+
__slots__ = ()
|
| 40 |
+
|
| 41 |
+
def serialize(self) -> str:
|
| 42 |
+
return f'"{self}"'
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class Op(Node):
|
| 46 |
+
__slots__ = ()
|
| 47 |
+
|
| 48 |
+
def serialize(self) -> str:
|
| 49 |
+
return str(self)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
MarkerLogical = Literal["and", "or"]
|
| 53 |
+
MarkerVar = Union[Variable, Value]
|
| 54 |
+
MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
|
| 55 |
+
MarkerAtom = Union[MarkerItem, Sequence["MarkerAtom"]]
|
| 56 |
+
MarkerList = List[Union["MarkerList", MarkerAtom, MarkerLogical]]
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class ParsedRequirement(NamedTuple):
|
| 60 |
+
name: str
|
| 61 |
+
url: str
|
| 62 |
+
extras: list[str]
|
| 63 |
+
specifier: str
|
| 64 |
+
marker: MarkerList | None
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
# --------------------------------------------------------------------------------------
|
| 68 |
+
# Recursive descent parser for dependency specifier
|
| 69 |
+
# --------------------------------------------------------------------------------------
|
| 70 |
+
def parse_requirement(source: str) -> ParsedRequirement:
|
| 71 |
+
return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES))
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:
|
| 75 |
+
"""
|
| 76 |
+
requirement = WS? IDENTIFIER WS? extras WS? requirement_details
|
| 77 |
+
"""
|
| 78 |
+
tokenizer.consume("WS")
|
| 79 |
+
|
| 80 |
+
name_token = tokenizer.expect(
|
| 81 |
+
"IDENTIFIER", expected="package name at the start of dependency specifier"
|
| 82 |
+
)
|
| 83 |
+
name = name_token.text
|
| 84 |
+
tokenizer.consume("WS")
|
| 85 |
+
|
| 86 |
+
extras = _parse_extras(tokenizer)
|
| 87 |
+
tokenizer.consume("WS")
|
| 88 |
+
|
| 89 |
+
url, specifier, marker = _parse_requirement_details(tokenizer)
|
| 90 |
+
tokenizer.expect("END", expected="end of dependency specifier")
|
| 91 |
+
|
| 92 |
+
return ParsedRequirement(name, url, extras, specifier, marker)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def _parse_requirement_details(
|
| 96 |
+
tokenizer: Tokenizer,
|
| 97 |
+
) -> tuple[str, str, MarkerList | None]:
|
| 98 |
+
"""
|
| 99 |
+
requirement_details = AT URL (WS requirement_marker?)?
|
| 100 |
+
| specifier WS? (requirement_marker)?
|
| 101 |
+
"""
|
| 102 |
+
|
| 103 |
+
specifier = ""
|
| 104 |
+
url = ""
|
| 105 |
+
marker = None
|
| 106 |
+
|
| 107 |
+
if tokenizer.check("AT"):
|
| 108 |
+
tokenizer.read()
|
| 109 |
+
tokenizer.consume("WS")
|
| 110 |
+
|
| 111 |
+
url_start = tokenizer.position
|
| 112 |
+
url = tokenizer.expect("URL", expected="URL after @").text
|
| 113 |
+
if tokenizer.check("END", peek=True):
|
| 114 |
+
return (url, specifier, marker)
|
| 115 |
+
|
| 116 |
+
tokenizer.expect("WS", expected="whitespace after URL")
|
| 117 |
+
|
| 118 |
+
# The input might end after whitespace.
|
| 119 |
+
if tokenizer.check("END", peek=True):
|
| 120 |
+
return (url, specifier, marker)
|
| 121 |
+
|
| 122 |
+
marker = _parse_requirement_marker(
|
| 123 |
+
tokenizer,
|
| 124 |
+
span_start=url_start,
|
| 125 |
+
expected="semicolon (after URL and whitespace)",
|
| 126 |
+
)
|
| 127 |
+
else:
|
| 128 |
+
specifier_start = tokenizer.position
|
| 129 |
+
specifier = _parse_specifier(tokenizer)
|
| 130 |
+
tokenizer.consume("WS")
|
| 131 |
+
|
| 132 |
+
if tokenizer.check("END", peek=True):
|
| 133 |
+
return (url, specifier, marker)
|
| 134 |
+
|
| 135 |
+
marker = _parse_requirement_marker(
|
| 136 |
+
tokenizer,
|
| 137 |
+
span_start=specifier_start,
|
| 138 |
+
expected=(
|
| 139 |
+
"comma (within version specifier), semicolon (after version specifier)"
|
| 140 |
+
if specifier
|
| 141 |
+
else "semicolon (after name with no version specifier)"
|
| 142 |
+
),
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
return (url, specifier, marker)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def _parse_requirement_marker(
|
| 149 |
+
tokenizer: Tokenizer, *, span_start: int, expected: str
|
| 150 |
+
) -> MarkerList:
|
| 151 |
+
"""
|
| 152 |
+
requirement_marker = SEMICOLON marker WS?
|
| 153 |
+
"""
|
| 154 |
+
|
| 155 |
+
if not tokenizer.check("SEMICOLON"):
|
| 156 |
+
tokenizer.raise_syntax_error(
|
| 157 |
+
f"Expected {expected} or end",
|
| 158 |
+
span_start=span_start,
|
| 159 |
+
span_end=None,
|
| 160 |
+
)
|
| 161 |
+
tokenizer.read()
|
| 162 |
+
|
| 163 |
+
marker = _parse_marker(tokenizer)
|
| 164 |
+
tokenizer.consume("WS")
|
| 165 |
+
|
| 166 |
+
return marker
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def _parse_extras(tokenizer: Tokenizer) -> list[str]:
|
| 170 |
+
"""
|
| 171 |
+
extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)?
|
| 172 |
+
"""
|
| 173 |
+
if not tokenizer.check("LEFT_BRACKET", peek=True):
|
| 174 |
+
return []
|
| 175 |
+
|
| 176 |
+
with tokenizer.enclosing_tokens(
|
| 177 |
+
"LEFT_BRACKET",
|
| 178 |
+
"RIGHT_BRACKET",
|
| 179 |
+
around="extras",
|
| 180 |
+
):
|
| 181 |
+
tokenizer.consume("WS")
|
| 182 |
+
extras = _parse_extras_list(tokenizer)
|
| 183 |
+
tokenizer.consume("WS")
|
| 184 |
+
|
| 185 |
+
return extras
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _parse_extras_list(tokenizer: Tokenizer) -> list[str]:
|
| 189 |
+
"""
|
| 190 |
+
extras_list = identifier (wsp* ',' wsp* identifier)*
|
| 191 |
+
"""
|
| 192 |
+
extras: list[str] = []
|
| 193 |
+
|
| 194 |
+
if not tokenizer.check("IDENTIFIER"):
|
| 195 |
+
return extras
|
| 196 |
+
|
| 197 |
+
extras.append(tokenizer.read().text)
|
| 198 |
+
|
| 199 |
+
while True:
|
| 200 |
+
tokenizer.consume("WS")
|
| 201 |
+
if tokenizer.check("IDENTIFIER", peek=True):
|
| 202 |
+
tokenizer.raise_syntax_error("Expected comma between extra names")
|
| 203 |
+
elif not tokenizer.check("COMMA"):
|
| 204 |
+
break
|
| 205 |
+
|
| 206 |
+
tokenizer.read()
|
| 207 |
+
tokenizer.consume("WS")
|
| 208 |
+
|
| 209 |
+
extra_token = tokenizer.expect("IDENTIFIER", expected="extra name after comma")
|
| 210 |
+
extras.append(extra_token.text)
|
| 211 |
+
|
| 212 |
+
return extras
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def _parse_specifier(tokenizer: Tokenizer) -> str:
|
| 216 |
+
"""
|
| 217 |
+
specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
|
| 218 |
+
| WS? version_many WS?
|
| 219 |
+
"""
|
| 220 |
+
with tokenizer.enclosing_tokens(
|
| 221 |
+
"LEFT_PARENTHESIS",
|
| 222 |
+
"RIGHT_PARENTHESIS",
|
| 223 |
+
around="version specifier",
|
| 224 |
+
):
|
| 225 |
+
tokenizer.consume("WS")
|
| 226 |
+
parsed_specifiers = _parse_version_many(tokenizer)
|
| 227 |
+
tokenizer.consume("WS")
|
| 228 |
+
|
| 229 |
+
return parsed_specifiers
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def _parse_version_many(tokenizer: Tokenizer) -> str:
|
| 233 |
+
"""
|
| 234 |
+
version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)?
|
| 235 |
+
"""
|
| 236 |
+
parsed_specifiers = ""
|
| 237 |
+
while tokenizer.check("SPECIFIER"):
|
| 238 |
+
span_start = tokenizer.position
|
| 239 |
+
parsed_specifiers += tokenizer.read().text
|
| 240 |
+
if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True):
|
| 241 |
+
tokenizer.raise_syntax_error(
|
| 242 |
+
".* suffix can only be used with `==` or `!=` operators",
|
| 243 |
+
span_start=span_start,
|
| 244 |
+
span_end=tokenizer.position + 1,
|
| 245 |
+
)
|
| 246 |
+
if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True):
|
| 247 |
+
tokenizer.raise_syntax_error(
|
| 248 |
+
"Local version label can only be used with `==` or `!=` operators",
|
| 249 |
+
span_start=span_start,
|
| 250 |
+
span_end=tokenizer.position,
|
| 251 |
+
)
|
| 252 |
+
tokenizer.consume("WS")
|
| 253 |
+
if not tokenizer.check("COMMA"):
|
| 254 |
+
break
|
| 255 |
+
parsed_specifiers += tokenizer.read().text
|
| 256 |
+
tokenizer.consume("WS")
|
| 257 |
+
|
| 258 |
+
return parsed_specifiers
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
# --------------------------------------------------------------------------------------
|
| 262 |
+
# Recursive descent parser for marker expression
|
| 263 |
+
# --------------------------------------------------------------------------------------
|
| 264 |
+
def parse_marker(source: str) -> MarkerList:
|
| 265 |
+
return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES))
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList:
|
| 269 |
+
retval = _parse_marker(tokenizer)
|
| 270 |
+
tokenizer.expect("END", expected="end of marker expression")
|
| 271 |
+
return retval
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
|
| 275 |
+
"""
|
| 276 |
+
marker = marker_atom (BOOLOP marker_atom)+
|
| 277 |
+
"""
|
| 278 |
+
expression = [_parse_marker_atom(tokenizer)]
|
| 279 |
+
while tokenizer.check("BOOLOP"):
|
| 280 |
+
token = tokenizer.read()
|
| 281 |
+
expr_right = _parse_marker_atom(tokenizer)
|
| 282 |
+
expression.extend((token.text, expr_right))
|
| 283 |
+
return expression
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:
|
| 287 |
+
"""
|
| 288 |
+
marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?
|
| 289 |
+
| WS? marker_item WS?
|
| 290 |
+
"""
|
| 291 |
+
|
| 292 |
+
tokenizer.consume("WS")
|
| 293 |
+
if tokenizer.check("LEFT_PARENTHESIS", peek=True):
|
| 294 |
+
with tokenizer.enclosing_tokens(
|
| 295 |
+
"LEFT_PARENTHESIS",
|
| 296 |
+
"RIGHT_PARENTHESIS",
|
| 297 |
+
around="marker expression",
|
| 298 |
+
):
|
| 299 |
+
tokenizer.consume("WS")
|
| 300 |
+
marker: MarkerAtom = _parse_marker(tokenizer)
|
| 301 |
+
tokenizer.consume("WS")
|
| 302 |
+
else:
|
| 303 |
+
marker = _parse_marker_item(tokenizer)
|
| 304 |
+
tokenizer.consume("WS")
|
| 305 |
+
return marker
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem:
|
| 309 |
+
"""
|
| 310 |
+
marker_item = WS? marker_var WS? marker_op WS? marker_var WS?
|
| 311 |
+
"""
|
| 312 |
+
tokenizer.consume("WS")
|
| 313 |
+
marker_var_left = _parse_marker_var(tokenizer)
|
| 314 |
+
tokenizer.consume("WS")
|
| 315 |
+
marker_op = _parse_marker_op(tokenizer)
|
| 316 |
+
tokenizer.consume("WS")
|
| 317 |
+
marker_var_right = _parse_marker_var(tokenizer)
|
| 318 |
+
tokenizer.consume("WS")
|
| 319 |
+
return (marker_var_left, marker_op, marker_var_right)
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar: # noqa: RET503
|
| 323 |
+
"""
|
| 324 |
+
marker_var = VARIABLE | QUOTED_STRING
|
| 325 |
+
"""
|
| 326 |
+
if tokenizer.check("VARIABLE"):
|
| 327 |
+
return process_env_var(tokenizer.read().text.replace(".", "_"))
|
| 328 |
+
elif tokenizer.check("QUOTED_STRING"):
|
| 329 |
+
return process_python_str(tokenizer.read().text)
|
| 330 |
+
else:
|
| 331 |
+
tokenizer.raise_syntax_error(
|
| 332 |
+
message="Expected a marker variable or quoted string"
|
| 333 |
+
)
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def process_env_var(env_var: str) -> Variable:
|
| 337 |
+
if env_var in ("platform_python_implementation", "python_implementation"):
|
| 338 |
+
return Variable("platform_python_implementation")
|
| 339 |
+
else:
|
| 340 |
+
return Variable(env_var)
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def process_python_str(python_str: str) -> Value:
|
| 344 |
+
value = ast.literal_eval(python_str)
|
| 345 |
+
return Value(str(value))
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def _parse_marker_op(tokenizer: Tokenizer) -> Op:
|
| 349 |
+
"""
|
| 350 |
+
marker_op = IN | NOT IN | OP
|
| 351 |
+
"""
|
| 352 |
+
if tokenizer.check("IN"):
|
| 353 |
+
tokenizer.read()
|
| 354 |
+
return Op("in")
|
| 355 |
+
elif tokenizer.check("NOT"):
|
| 356 |
+
tokenizer.read()
|
| 357 |
+
tokenizer.expect("WS", expected="whitespace after 'not'")
|
| 358 |
+
tokenizer.expect("IN", expected="'in' after 'not'")
|
| 359 |
+
return Op("not in")
|
| 360 |
+
elif tokenizer.check("OP"):
|
| 361 |
+
return Op(tokenizer.read().text)
|
| 362 |
+
else:
|
| 363 |
+
return tokenizer.raise_syntax_error(
|
| 364 |
+
"Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in"
|
| 365 |
+
)
|
.venv/lib/python3.13/site-packages/packaging/_structures.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
|
| 5 |
+
import typing
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@typing.final
|
| 9 |
+
class InfinityType:
|
| 10 |
+
__slots__ = ()
|
| 11 |
+
|
| 12 |
+
def __repr__(self) -> str:
|
| 13 |
+
return "Infinity"
|
| 14 |
+
|
| 15 |
+
def __hash__(self) -> int:
|
| 16 |
+
return hash(repr(self))
|
| 17 |
+
|
| 18 |
+
def __lt__(self, other: object) -> bool:
|
| 19 |
+
return False
|
| 20 |
+
|
| 21 |
+
def __le__(self, other: object) -> bool:
|
| 22 |
+
return False
|
| 23 |
+
|
| 24 |
+
def __eq__(self, other: object) -> bool:
|
| 25 |
+
return isinstance(other, self.__class__)
|
| 26 |
+
|
| 27 |
+
def __gt__(self, other: object) -> bool:
|
| 28 |
+
return True
|
| 29 |
+
|
| 30 |
+
def __ge__(self, other: object) -> bool:
|
| 31 |
+
return True
|
| 32 |
+
|
| 33 |
+
def __neg__(self: object) -> "NegativeInfinityType":
|
| 34 |
+
return NegativeInfinity
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
Infinity = InfinityType()
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@typing.final
|
| 41 |
+
class NegativeInfinityType:
|
| 42 |
+
__slots__ = ()
|
| 43 |
+
|
| 44 |
+
def __repr__(self) -> str:
|
| 45 |
+
return "-Infinity"
|
| 46 |
+
|
| 47 |
+
def __hash__(self) -> int:
|
| 48 |
+
return hash(repr(self))
|
| 49 |
+
|
| 50 |
+
def __lt__(self, other: object) -> bool:
|
| 51 |
+
return True
|
| 52 |
+
|
| 53 |
+
def __le__(self, other: object) -> bool:
|
| 54 |
+
return True
|
| 55 |
+
|
| 56 |
+
def __eq__(self, other: object) -> bool:
|
| 57 |
+
return isinstance(other, self.__class__)
|
| 58 |
+
|
| 59 |
+
def __gt__(self, other: object) -> bool:
|
| 60 |
+
return False
|
| 61 |
+
|
| 62 |
+
def __ge__(self, other: object) -> bool:
|
| 63 |
+
return False
|
| 64 |
+
|
| 65 |
+
def __neg__(self: object) -> InfinityType:
|
| 66 |
+
return Infinity
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
NegativeInfinity = NegativeInfinityType()
|
.venv/lib/python3.13/site-packages/packaging/_tokenizer.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import contextlib
|
| 4 |
+
import re
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Generator, Mapping, NoReturn
|
| 7 |
+
|
| 8 |
+
from .specifiers import Specifier
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass
|
| 12 |
+
class Token:
|
| 13 |
+
name: str
|
| 14 |
+
text: str
|
| 15 |
+
position: int
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class ParserSyntaxError(Exception):
|
| 19 |
+
"""The provided source text could not be parsed correctly."""
|
| 20 |
+
|
| 21 |
+
def __init__(
|
| 22 |
+
self,
|
| 23 |
+
message: str,
|
| 24 |
+
*,
|
| 25 |
+
source: str,
|
| 26 |
+
span: tuple[int, int],
|
| 27 |
+
) -> None:
|
| 28 |
+
self.span = span
|
| 29 |
+
self.message = message
|
| 30 |
+
self.source = source
|
| 31 |
+
|
| 32 |
+
super().__init__()
|
| 33 |
+
|
| 34 |
+
def __str__(self) -> str:
|
| 35 |
+
marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^"
|
| 36 |
+
return f"{self.message}\n {self.source}\n {marker}"
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
DEFAULT_RULES: dict[str, re.Pattern[str]] = {
|
| 40 |
+
"LEFT_PARENTHESIS": re.compile(r"\("),
|
| 41 |
+
"RIGHT_PARENTHESIS": re.compile(r"\)"),
|
| 42 |
+
"LEFT_BRACKET": re.compile(r"\["),
|
| 43 |
+
"RIGHT_BRACKET": re.compile(r"\]"),
|
| 44 |
+
"SEMICOLON": re.compile(r";"),
|
| 45 |
+
"COMMA": re.compile(r","),
|
| 46 |
+
"QUOTED_STRING": re.compile(
|
| 47 |
+
r"""
|
| 48 |
+
(
|
| 49 |
+
('[^']*')
|
| 50 |
+
|
|
| 51 |
+
("[^"]*")
|
| 52 |
+
)
|
| 53 |
+
""",
|
| 54 |
+
re.VERBOSE,
|
| 55 |
+
),
|
| 56 |
+
"OP": re.compile(r"(===|==|~=|!=|<=|>=|<|>)"),
|
| 57 |
+
"BOOLOP": re.compile(r"\b(or|and)\b"),
|
| 58 |
+
"IN": re.compile(r"\bin\b"),
|
| 59 |
+
"NOT": re.compile(r"\bnot\b"),
|
| 60 |
+
"VARIABLE": re.compile(
|
| 61 |
+
r"""
|
| 62 |
+
\b(
|
| 63 |
+
python_version
|
| 64 |
+
|python_full_version
|
| 65 |
+
|os[._]name
|
| 66 |
+
|sys[._]platform
|
| 67 |
+
|platform_(release|system)
|
| 68 |
+
|platform[._](version|machine|python_implementation)
|
| 69 |
+
|python_implementation
|
| 70 |
+
|implementation_(name|version)
|
| 71 |
+
|extras?
|
| 72 |
+
|dependency_groups
|
| 73 |
+
)\b
|
| 74 |
+
""",
|
| 75 |
+
re.VERBOSE,
|
| 76 |
+
),
|
| 77 |
+
"SPECIFIER": re.compile(
|
| 78 |
+
Specifier._operator_regex_str + Specifier._version_regex_str,
|
| 79 |
+
re.VERBOSE | re.IGNORECASE,
|
| 80 |
+
),
|
| 81 |
+
"AT": re.compile(r"\@"),
|
| 82 |
+
"URL": re.compile(r"[^ \t]+"),
|
| 83 |
+
"IDENTIFIER": re.compile(r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b"),
|
| 84 |
+
"VERSION_PREFIX_TRAIL": re.compile(r"\.\*"),
|
| 85 |
+
"VERSION_LOCAL_LABEL_TRAIL": re.compile(r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*"),
|
| 86 |
+
"WS": re.compile(r"[ \t]+"),
|
| 87 |
+
"END": re.compile(r"$"),
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class Tokenizer:
|
| 92 |
+
"""Context-sensitive token parsing.
|
| 93 |
+
|
| 94 |
+
Provides methods to examine the input stream to check whether the next token
|
| 95 |
+
matches.
|
| 96 |
+
"""
|
| 97 |
+
|
| 98 |
+
def __init__(
|
| 99 |
+
self,
|
| 100 |
+
source: str,
|
| 101 |
+
*,
|
| 102 |
+
rules: Mapping[str, re.Pattern[str]],
|
| 103 |
+
) -> None:
|
| 104 |
+
self.source = source
|
| 105 |
+
self.rules = rules
|
| 106 |
+
self.next_token: Token | None = None
|
| 107 |
+
self.position = 0
|
| 108 |
+
|
| 109 |
+
def consume(self, name: str) -> None:
|
| 110 |
+
"""Move beyond provided token name, if at current position."""
|
| 111 |
+
if self.check(name):
|
| 112 |
+
self.read()
|
| 113 |
+
|
| 114 |
+
def check(self, name: str, *, peek: bool = False) -> bool:
|
| 115 |
+
"""Check whether the next token has the provided name.
|
| 116 |
+
|
| 117 |
+
By default, if the check succeeds, the token *must* be read before
|
| 118 |
+
another check. If `peek` is set to `True`, the token is not loaded and
|
| 119 |
+
would need to be checked again.
|
| 120 |
+
"""
|
| 121 |
+
assert self.next_token is None, (
|
| 122 |
+
f"Cannot check for {name!r}, already have {self.next_token!r}"
|
| 123 |
+
)
|
| 124 |
+
assert name in self.rules, f"Unknown token name: {name!r}"
|
| 125 |
+
|
| 126 |
+
expression = self.rules[name]
|
| 127 |
+
|
| 128 |
+
match = expression.match(self.source, self.position)
|
| 129 |
+
if match is None:
|
| 130 |
+
return False
|
| 131 |
+
if not peek:
|
| 132 |
+
self.next_token = Token(name, match[0], self.position)
|
| 133 |
+
return True
|
| 134 |
+
|
| 135 |
+
def expect(self, name: str, *, expected: str) -> Token:
|
| 136 |
+
"""Expect a certain token name next, failing with a syntax error otherwise.
|
| 137 |
+
|
| 138 |
+
The token is *not* read.
|
| 139 |
+
"""
|
| 140 |
+
if not self.check(name):
|
| 141 |
+
raise self.raise_syntax_error(f"Expected {expected}")
|
| 142 |
+
return self.read()
|
| 143 |
+
|
| 144 |
+
def read(self) -> Token:
|
| 145 |
+
"""Consume the next token and return it."""
|
| 146 |
+
token = self.next_token
|
| 147 |
+
assert token is not None
|
| 148 |
+
|
| 149 |
+
self.position += len(token.text)
|
| 150 |
+
self.next_token = None
|
| 151 |
+
|
| 152 |
+
return token
|
| 153 |
+
|
| 154 |
+
def raise_syntax_error(
|
| 155 |
+
self,
|
| 156 |
+
message: str,
|
| 157 |
+
*,
|
| 158 |
+
span_start: int | None = None,
|
| 159 |
+
span_end: int | None = None,
|
| 160 |
+
) -> NoReturn:
|
| 161 |
+
"""Raise ParserSyntaxError at the given position."""
|
| 162 |
+
span = (
|
| 163 |
+
self.position if span_start is None else span_start,
|
| 164 |
+
self.position if span_end is None else span_end,
|
| 165 |
+
)
|
| 166 |
+
raise ParserSyntaxError(
|
| 167 |
+
message,
|
| 168 |
+
source=self.source,
|
| 169 |
+
span=span,
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
@contextlib.contextmanager
|
| 173 |
+
def enclosing_tokens(
|
| 174 |
+
self, open_token: str, close_token: str, *, around: str
|
| 175 |
+
) -> Generator[None, None, None]:
|
| 176 |
+
if self.check(open_token):
|
| 177 |
+
open_position = self.position
|
| 178 |
+
self.read()
|
| 179 |
+
else:
|
| 180 |
+
open_position = None
|
| 181 |
+
|
| 182 |
+
yield
|
| 183 |
+
|
| 184 |
+
if open_position is None:
|
| 185 |
+
return
|
| 186 |
+
|
| 187 |
+
if not self.check(close_token):
|
| 188 |
+
self.raise_syntax_error(
|
| 189 |
+
f"Expected matching {close_token} for {open_token}, after {around}",
|
| 190 |
+
span_start=open_position,
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
self.read()
|
.venv/lib/python3.13/site-packages/packaging/markers.py
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import operator
|
| 8 |
+
import os
|
| 9 |
+
import platform
|
| 10 |
+
import sys
|
| 11 |
+
from typing import AbstractSet, Callable, Literal, Mapping, TypedDict, Union, cast
|
| 12 |
+
|
| 13 |
+
from ._parser import MarkerAtom, MarkerList, Op, Value, Variable
|
| 14 |
+
from ._parser import parse_marker as _parse_marker
|
| 15 |
+
from ._tokenizer import ParserSyntaxError
|
| 16 |
+
from .specifiers import InvalidSpecifier, Specifier
|
| 17 |
+
from .utils import canonicalize_name
|
| 18 |
+
|
| 19 |
+
__all__ = [
|
| 20 |
+
"Environment",
|
| 21 |
+
"EvaluateContext",
|
| 22 |
+
"InvalidMarker",
|
| 23 |
+
"Marker",
|
| 24 |
+
"UndefinedComparison",
|
| 25 |
+
"UndefinedEnvironmentName",
|
| 26 |
+
"default_environment",
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
Operator = Callable[[str, Union[str, AbstractSet[str]]], bool]
|
| 30 |
+
EvaluateContext = Literal["metadata", "lock_file", "requirement"]
|
| 31 |
+
MARKERS_ALLOWING_SET = {"extras", "dependency_groups"}
|
| 32 |
+
MARKERS_REQUIRING_VERSION = {
|
| 33 |
+
"implementation_version",
|
| 34 |
+
"platform_release",
|
| 35 |
+
"python_full_version",
|
| 36 |
+
"python_version",
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class InvalidMarker(ValueError):
|
| 41 |
+
"""
|
| 42 |
+
An invalid marker was found, users should refer to PEP 508.
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class UndefinedComparison(ValueError):
|
| 47 |
+
"""
|
| 48 |
+
An invalid operation was attempted on a value that doesn't support it.
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class UndefinedEnvironmentName(ValueError):
|
| 53 |
+
"""
|
| 54 |
+
A name was attempted to be used that does not exist inside of the
|
| 55 |
+
environment.
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class Environment(TypedDict):
|
| 60 |
+
implementation_name: str
|
| 61 |
+
"""The implementation's identifier, e.g. ``'cpython'``."""
|
| 62 |
+
|
| 63 |
+
implementation_version: str
|
| 64 |
+
"""
|
| 65 |
+
The implementation's version, e.g. ``'3.13.0a2'`` for CPython 3.13.0a2, or
|
| 66 |
+
``'7.3.13'`` for PyPy3.10 v7.3.13.
|
| 67 |
+
"""
|
| 68 |
+
|
| 69 |
+
os_name: str
|
| 70 |
+
"""
|
| 71 |
+
The value of :py:data:`os.name`. The name of the operating system dependent module
|
| 72 |
+
imported, e.g. ``'posix'``.
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
platform_machine: str
|
| 76 |
+
"""
|
| 77 |
+
Returns the machine type, e.g. ``'i386'``.
|
| 78 |
+
|
| 79 |
+
An empty string if the value cannot be determined.
|
| 80 |
+
"""
|
| 81 |
+
|
| 82 |
+
platform_release: str
|
| 83 |
+
"""
|
| 84 |
+
The system's release, e.g. ``'2.2.0'`` or ``'NT'``.
|
| 85 |
+
|
| 86 |
+
An empty string if the value cannot be determined.
|
| 87 |
+
"""
|
| 88 |
+
|
| 89 |
+
platform_system: str
|
| 90 |
+
"""
|
| 91 |
+
The system/OS name, e.g. ``'Linux'``, ``'Windows'`` or ``'Java'``.
|
| 92 |
+
|
| 93 |
+
An empty string if the value cannot be determined.
|
| 94 |
+
"""
|
| 95 |
+
|
| 96 |
+
platform_version: str
|
| 97 |
+
"""
|
| 98 |
+
The system's release version, e.g. ``'#3 on degas'``.
|
| 99 |
+
|
| 100 |
+
An empty string if the value cannot be determined.
|
| 101 |
+
"""
|
| 102 |
+
|
| 103 |
+
python_full_version: str
|
| 104 |
+
"""
|
| 105 |
+
The Python version as string ``'major.minor.patchlevel'``.
|
| 106 |
+
|
| 107 |
+
Note that unlike the Python :py:data:`sys.version`, this value will always include
|
| 108 |
+
the patchlevel (it defaults to 0).
|
| 109 |
+
"""
|
| 110 |
+
|
| 111 |
+
platform_python_implementation: str
|
| 112 |
+
"""
|
| 113 |
+
A string identifying the Python implementation, e.g. ``'CPython'``.
|
| 114 |
+
"""
|
| 115 |
+
|
| 116 |
+
python_version: str
|
| 117 |
+
"""The Python version as string ``'major.minor'``."""
|
| 118 |
+
|
| 119 |
+
sys_platform: str
|
| 120 |
+
"""
|
| 121 |
+
This string contains a platform identifier that can be used to append
|
| 122 |
+
platform-specific components to :py:data:`sys.path`, for instance.
|
| 123 |
+
|
| 124 |
+
For Unix systems, except on Linux and AIX, this is the lowercased OS name as
|
| 125 |
+
returned by ``uname -s`` with the first part of the version as returned by
|
| 126 |
+
``uname -r`` appended, e.g. ``'sunos5'`` or ``'freebsd8'``, at the time when Python
|
| 127 |
+
was built.
|
| 128 |
+
"""
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def _normalize_extras(
|
| 132 |
+
result: MarkerList | MarkerAtom | str,
|
| 133 |
+
) -> MarkerList | MarkerAtom | str:
|
| 134 |
+
if not isinstance(result, tuple):
|
| 135 |
+
return result
|
| 136 |
+
|
| 137 |
+
lhs, op, rhs = result
|
| 138 |
+
if isinstance(lhs, Variable) and lhs.value == "extra":
|
| 139 |
+
normalized_extra = canonicalize_name(rhs.value)
|
| 140 |
+
rhs = Value(normalized_extra)
|
| 141 |
+
elif isinstance(rhs, Variable) and rhs.value == "extra":
|
| 142 |
+
normalized_extra = canonicalize_name(lhs.value)
|
| 143 |
+
lhs = Value(normalized_extra)
|
| 144 |
+
return lhs, op, rhs
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def _normalize_extra_values(results: MarkerList) -> MarkerList:
|
| 148 |
+
"""
|
| 149 |
+
Normalize extra values.
|
| 150 |
+
"""
|
| 151 |
+
|
| 152 |
+
return [_normalize_extras(r) for r in results]
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def _format_marker(
|
| 156 |
+
marker: list[str] | MarkerAtom | str, first: bool | None = True
|
| 157 |
+
) -> str:
|
| 158 |
+
assert isinstance(marker, (list, tuple, str))
|
| 159 |
+
|
| 160 |
+
# Sometimes we have a structure like [[...]] which is a single item list
|
| 161 |
+
# where the single item is itself it's own list. In that case we want skip
|
| 162 |
+
# the rest of this function so that we don't get extraneous () on the
|
| 163 |
+
# outside.
|
| 164 |
+
if (
|
| 165 |
+
isinstance(marker, list)
|
| 166 |
+
and len(marker) == 1
|
| 167 |
+
and isinstance(marker[0], (list, tuple))
|
| 168 |
+
):
|
| 169 |
+
return _format_marker(marker[0])
|
| 170 |
+
|
| 171 |
+
if isinstance(marker, list):
|
| 172 |
+
inner = (_format_marker(m, first=False) for m in marker)
|
| 173 |
+
if first:
|
| 174 |
+
return " ".join(inner)
|
| 175 |
+
else:
|
| 176 |
+
return "(" + " ".join(inner) + ")"
|
| 177 |
+
elif isinstance(marker, tuple):
|
| 178 |
+
return " ".join([m.serialize() for m in marker])
|
| 179 |
+
else:
|
| 180 |
+
return marker
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
_operators: dict[str, Operator] = {
|
| 184 |
+
"in": lambda lhs, rhs: lhs in rhs,
|
| 185 |
+
"not in": lambda lhs, rhs: lhs not in rhs,
|
| 186 |
+
"<": lambda _lhs, _rhs: False,
|
| 187 |
+
"<=": operator.eq,
|
| 188 |
+
"==": operator.eq,
|
| 189 |
+
"!=": operator.ne,
|
| 190 |
+
">=": operator.eq,
|
| 191 |
+
">": lambda _lhs, _rhs: False,
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def _eval_op(lhs: str, op: Op, rhs: str | AbstractSet[str], *, key: str) -> bool:
|
| 196 |
+
op_str = op.serialize()
|
| 197 |
+
if key in MARKERS_REQUIRING_VERSION:
|
| 198 |
+
try:
|
| 199 |
+
spec = Specifier(f"{op_str}{rhs}")
|
| 200 |
+
except InvalidSpecifier:
|
| 201 |
+
pass
|
| 202 |
+
else:
|
| 203 |
+
return spec.contains(lhs, prereleases=True)
|
| 204 |
+
|
| 205 |
+
oper: Operator | None = _operators.get(op_str)
|
| 206 |
+
if oper is None:
|
| 207 |
+
raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")
|
| 208 |
+
|
| 209 |
+
return oper(lhs, rhs)
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def _normalize(
|
| 213 |
+
lhs: str, rhs: str | AbstractSet[str], key: str
|
| 214 |
+
) -> tuple[str, str | AbstractSet[str]]:
|
| 215 |
+
# PEP 685 - Comparison of extra names for optional distribution dependencies
|
| 216 |
+
# https://peps.python.org/pep-0685/
|
| 217 |
+
# > When comparing extra names, tools MUST normalize the names being
|
| 218 |
+
# > compared using the semantics outlined in PEP 503 for names
|
| 219 |
+
if key == "extra":
|
| 220 |
+
assert isinstance(rhs, str), "extra value must be a string"
|
| 221 |
+
# Both sides are normalized at this point already
|
| 222 |
+
return (lhs, rhs)
|
| 223 |
+
if key in MARKERS_ALLOWING_SET:
|
| 224 |
+
if isinstance(rhs, str): # pragma: no cover
|
| 225 |
+
return (canonicalize_name(lhs), canonicalize_name(rhs))
|
| 226 |
+
else:
|
| 227 |
+
return (canonicalize_name(lhs), {canonicalize_name(v) for v in rhs})
|
| 228 |
+
|
| 229 |
+
# other environment markers don't have such standards
|
| 230 |
+
return lhs, rhs
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def _evaluate_markers(
|
| 234 |
+
markers: MarkerList, environment: dict[str, str | AbstractSet[str]]
|
| 235 |
+
) -> bool:
|
| 236 |
+
groups: list[list[bool]] = [[]]
|
| 237 |
+
|
| 238 |
+
for marker in markers:
|
| 239 |
+
if isinstance(marker, list):
|
| 240 |
+
groups[-1].append(_evaluate_markers(marker, environment))
|
| 241 |
+
elif isinstance(marker, tuple):
|
| 242 |
+
lhs, op, rhs = marker
|
| 243 |
+
|
| 244 |
+
if isinstance(lhs, Variable):
|
| 245 |
+
environment_key = lhs.value
|
| 246 |
+
lhs_value = environment[environment_key]
|
| 247 |
+
rhs_value = rhs.value
|
| 248 |
+
else:
|
| 249 |
+
lhs_value = lhs.value
|
| 250 |
+
environment_key = rhs.value
|
| 251 |
+
rhs_value = environment[environment_key]
|
| 252 |
+
|
| 253 |
+
assert isinstance(lhs_value, str), "lhs must be a string"
|
| 254 |
+
lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
|
| 255 |
+
groups[-1].append(_eval_op(lhs_value, op, rhs_value, key=environment_key))
|
| 256 |
+
elif marker == "or":
|
| 257 |
+
groups.append([])
|
| 258 |
+
elif marker == "and":
|
| 259 |
+
pass
|
| 260 |
+
else: # pragma: nocover
|
| 261 |
+
raise TypeError(f"Unexpected marker {marker!r}")
|
| 262 |
+
|
| 263 |
+
return any(all(item) for item in groups)
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
def format_full_version(info: sys._version_info) -> str:
|
| 267 |
+
version = f"{info.major}.{info.minor}.{info.micro}"
|
| 268 |
+
kind = info.releaselevel
|
| 269 |
+
if kind != "final":
|
| 270 |
+
version += kind[0] + str(info.serial)
|
| 271 |
+
return version
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def default_environment() -> Environment:
|
| 275 |
+
iver = format_full_version(sys.implementation.version)
|
| 276 |
+
implementation_name = sys.implementation.name
|
| 277 |
+
return {
|
| 278 |
+
"implementation_name": implementation_name,
|
| 279 |
+
"implementation_version": iver,
|
| 280 |
+
"os_name": os.name,
|
| 281 |
+
"platform_machine": platform.machine(),
|
| 282 |
+
"platform_release": platform.release(),
|
| 283 |
+
"platform_system": platform.system(),
|
| 284 |
+
"platform_version": platform.version(),
|
| 285 |
+
"python_full_version": platform.python_version(),
|
| 286 |
+
"platform_python_implementation": platform.python_implementation(),
|
| 287 |
+
"python_version": ".".join(platform.python_version_tuple()[:2]),
|
| 288 |
+
"sys_platform": sys.platform,
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
class Marker:
|
| 293 |
+
def __init__(self, marker: str) -> None:
|
| 294 |
+
# Note: We create a Marker object without calling this constructor in
|
| 295 |
+
# packaging.requirements.Requirement. If any additional logic is
|
| 296 |
+
# added here, make sure to mirror/adapt Requirement.
|
| 297 |
+
|
| 298 |
+
# If this fails and throws an error, the repr still expects _markers to
|
| 299 |
+
# be defined.
|
| 300 |
+
self._markers: MarkerList = []
|
| 301 |
+
|
| 302 |
+
try:
|
| 303 |
+
self._markers = _normalize_extra_values(_parse_marker(marker))
|
| 304 |
+
# The attribute `_markers` can be described in terms of a recursive type:
|
| 305 |
+
# MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]]
|
| 306 |
+
#
|
| 307 |
+
# For example, the following expression:
|
| 308 |
+
# python_version > "3.6" or (python_version == "3.6" and os_name == "unix")
|
| 309 |
+
#
|
| 310 |
+
# is parsed into:
|
| 311 |
+
# [
|
| 312 |
+
# (<Variable('python_version')>, <Op('>')>, <Value('3.6')>),
|
| 313 |
+
# 'and',
|
| 314 |
+
# [
|
| 315 |
+
# (<Variable('python_version')>, <Op('==')>, <Value('3.6')>),
|
| 316 |
+
# 'or',
|
| 317 |
+
# (<Variable('os_name')>, <Op('==')>, <Value('unix')>)
|
| 318 |
+
# ]
|
| 319 |
+
# ]
|
| 320 |
+
except ParserSyntaxError as e:
|
| 321 |
+
raise InvalidMarker(str(e)) from e
|
| 322 |
+
|
| 323 |
+
def __str__(self) -> str:
|
| 324 |
+
return _format_marker(self._markers)
|
| 325 |
+
|
| 326 |
+
def __repr__(self) -> str:
|
| 327 |
+
return f"<{self.__class__.__name__}('{self}')>"
|
| 328 |
+
|
| 329 |
+
def __hash__(self) -> int:
|
| 330 |
+
return hash(str(self))
|
| 331 |
+
|
| 332 |
+
def __eq__(self, other: object) -> bool:
|
| 333 |
+
if not isinstance(other, Marker):
|
| 334 |
+
return NotImplemented
|
| 335 |
+
|
| 336 |
+
return str(self) == str(other)
|
| 337 |
+
|
| 338 |
+
def evaluate(
|
| 339 |
+
self,
|
| 340 |
+
environment: Mapping[str, str | AbstractSet[str]] | None = None,
|
| 341 |
+
context: EvaluateContext = "metadata",
|
| 342 |
+
) -> bool:
|
| 343 |
+
"""Evaluate a marker.
|
| 344 |
+
|
| 345 |
+
Return the boolean from evaluating the given marker against the
|
| 346 |
+
environment. environment is an optional argument to override all or
|
| 347 |
+
part of the determined environment. The *context* parameter specifies what
|
| 348 |
+
context the markers are being evaluated for, which influences what markers
|
| 349 |
+
are considered valid. Acceptable values are "metadata" (for core metadata;
|
| 350 |
+
default), "lock_file", and "requirement" (i.e. all other situations).
|
| 351 |
+
|
| 352 |
+
The environment is determined from the current Python process.
|
| 353 |
+
"""
|
| 354 |
+
current_environment = cast(
|
| 355 |
+
"dict[str, str | AbstractSet[str]]", default_environment()
|
| 356 |
+
)
|
| 357 |
+
if context == "lock_file":
|
| 358 |
+
current_environment.update(
|
| 359 |
+
extras=frozenset(), dependency_groups=frozenset()
|
| 360 |
+
)
|
| 361 |
+
elif context == "metadata":
|
| 362 |
+
current_environment["extra"] = ""
|
| 363 |
+
|
| 364 |
+
if environment is not None:
|
| 365 |
+
current_environment.update(environment)
|
| 366 |
+
if "extra" in current_environment:
|
| 367 |
+
# The API used to allow setting extra to None. We need to handle
|
| 368 |
+
# this case for backwards compatibility. Also skip running
|
| 369 |
+
# normalize name if extra is empty.
|
| 370 |
+
extra = cast("str | None", current_environment["extra"])
|
| 371 |
+
current_environment["extra"] = canonicalize_name(extra) if extra else ""
|
| 372 |
+
|
| 373 |
+
return _evaluate_markers(
|
| 374 |
+
self._markers, _repair_python_full_version(current_environment)
|
| 375 |
+
)
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
def _repair_python_full_version(
|
| 379 |
+
env: dict[str, str | AbstractSet[str]],
|
| 380 |
+
) -> dict[str, str | AbstractSet[str]]:
|
| 381 |
+
"""
|
| 382 |
+
Work around platform.python_version() returning something that is not PEP 440
|
| 383 |
+
compliant for non-tagged Python builds.
|
| 384 |
+
"""
|
| 385 |
+
python_full_version = cast("str", env["python_full_version"])
|
| 386 |
+
if python_full_version.endswith("+"):
|
| 387 |
+
env["python_full_version"] = f"{python_full_version}local"
|
| 388 |
+
return env
|
.venv/lib/python3.13/site-packages/packaging/metadata.py
ADDED
|
@@ -0,0 +1,978 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import email.feedparser
|
| 4 |
+
import email.header
|
| 5 |
+
import email.message
|
| 6 |
+
import email.parser
|
| 7 |
+
import email.policy
|
| 8 |
+
import keyword
|
| 9 |
+
import pathlib
|
| 10 |
+
import sys
|
| 11 |
+
import typing
|
| 12 |
+
from typing import (
|
| 13 |
+
Any,
|
| 14 |
+
Callable,
|
| 15 |
+
Generic,
|
| 16 |
+
Literal,
|
| 17 |
+
TypedDict,
|
| 18 |
+
cast,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
from . import licenses, requirements, specifiers, utils
|
| 22 |
+
from . import version as version_module
|
| 23 |
+
|
| 24 |
+
if typing.TYPE_CHECKING:
|
| 25 |
+
from .licenses import NormalizedLicenseExpression
|
| 26 |
+
|
| 27 |
+
T = typing.TypeVar("T")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
if sys.version_info >= (3, 11): # pragma: no cover
|
| 31 |
+
ExceptionGroup = ExceptionGroup # noqa: F821
|
| 32 |
+
else: # pragma: no cover
|
| 33 |
+
|
| 34 |
+
class ExceptionGroup(Exception):
|
| 35 |
+
"""A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11.
|
| 36 |
+
|
| 37 |
+
If :external:exc:`ExceptionGroup` is already defined by Python itself,
|
| 38 |
+
that version is used instead.
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
message: str
|
| 42 |
+
exceptions: list[Exception]
|
| 43 |
+
|
| 44 |
+
def __init__(self, message: str, exceptions: list[Exception]) -> None:
|
| 45 |
+
self.message = message
|
| 46 |
+
self.exceptions = exceptions
|
| 47 |
+
|
| 48 |
+
def __repr__(self) -> str:
|
| 49 |
+
return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})"
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class InvalidMetadata(ValueError):
|
| 53 |
+
"""A metadata field contains invalid data."""
|
| 54 |
+
|
| 55 |
+
field: str
|
| 56 |
+
"""The name of the field that contains invalid data."""
|
| 57 |
+
|
| 58 |
+
def __init__(self, field: str, message: str) -> None:
|
| 59 |
+
self.field = field
|
| 60 |
+
super().__init__(message)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
# The RawMetadata class attempts to make as few assumptions about the underlying
|
| 64 |
+
# serialization formats as possible. The idea is that as long as a serialization
|
| 65 |
+
# formats offer some very basic primitives in *some* way then we can support
|
| 66 |
+
# serializing to and from that format.
|
| 67 |
+
class RawMetadata(TypedDict, total=False):
|
| 68 |
+
"""A dictionary of raw core metadata.
|
| 69 |
+
|
| 70 |
+
Each field in core metadata maps to a key of this dictionary (when data is
|
| 71 |
+
provided). The key is lower-case and underscores are used instead of dashes
|
| 72 |
+
compared to the equivalent core metadata field. Any core metadata field that
|
| 73 |
+
can be specified multiple times or can hold multiple values in a single
|
| 74 |
+
field have a key with a plural name. See :class:`Metadata` whose attributes
|
| 75 |
+
match the keys of this dictionary.
|
| 76 |
+
|
| 77 |
+
Core metadata fields that can be specified multiple times are stored as a
|
| 78 |
+
list or dict depending on which is appropriate for the field. Any fields
|
| 79 |
+
which hold multiple values in a single field are stored as a list.
|
| 80 |
+
|
| 81 |
+
"""
|
| 82 |
+
|
| 83 |
+
# Metadata 1.0 - PEP 241
|
| 84 |
+
metadata_version: str
|
| 85 |
+
name: str
|
| 86 |
+
version: str
|
| 87 |
+
platforms: list[str]
|
| 88 |
+
summary: str
|
| 89 |
+
description: str
|
| 90 |
+
keywords: list[str]
|
| 91 |
+
home_page: str
|
| 92 |
+
author: str
|
| 93 |
+
author_email: str
|
| 94 |
+
license: str
|
| 95 |
+
|
| 96 |
+
# Metadata 1.1 - PEP 314
|
| 97 |
+
supported_platforms: list[str]
|
| 98 |
+
download_url: str
|
| 99 |
+
classifiers: list[str]
|
| 100 |
+
requires: list[str]
|
| 101 |
+
provides: list[str]
|
| 102 |
+
obsoletes: list[str]
|
| 103 |
+
|
| 104 |
+
# Metadata 1.2 - PEP 345
|
| 105 |
+
maintainer: str
|
| 106 |
+
maintainer_email: str
|
| 107 |
+
requires_dist: list[str]
|
| 108 |
+
provides_dist: list[str]
|
| 109 |
+
obsoletes_dist: list[str]
|
| 110 |
+
requires_python: str
|
| 111 |
+
requires_external: list[str]
|
| 112 |
+
project_urls: dict[str, str]
|
| 113 |
+
|
| 114 |
+
# Metadata 2.0
|
| 115 |
+
# PEP 426 attempted to completely revamp the metadata format
|
| 116 |
+
# but got stuck without ever being able to build consensus on
|
| 117 |
+
# it and ultimately ended up withdrawn.
|
| 118 |
+
#
|
| 119 |
+
# However, a number of tools had started emitting METADATA with
|
| 120 |
+
# `2.0` Metadata-Version, so for historical reasons, this version
|
| 121 |
+
# was skipped.
|
| 122 |
+
|
| 123 |
+
# Metadata 2.1 - PEP 566
|
| 124 |
+
description_content_type: str
|
| 125 |
+
provides_extra: list[str]
|
| 126 |
+
|
| 127 |
+
# Metadata 2.2 - PEP 643
|
| 128 |
+
dynamic: list[str]
|
| 129 |
+
|
| 130 |
+
# Metadata 2.3 - PEP 685
|
| 131 |
+
# No new fields were added in PEP 685, just some edge case were
|
| 132 |
+
# tightened up to provide better interoperability.
|
| 133 |
+
|
| 134 |
+
# Metadata 2.4 - PEP 639
|
| 135 |
+
license_expression: str
|
| 136 |
+
license_files: list[str]
|
| 137 |
+
|
| 138 |
+
# Metadata 2.5 - PEP 794
|
| 139 |
+
import_names: list[str]
|
| 140 |
+
import_namespaces: list[str]
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
# 'keywords' is special as it's a string in the core metadata spec, but we
|
| 144 |
+
# represent it as a list.
|
| 145 |
+
_STRING_FIELDS = {
|
| 146 |
+
"author",
|
| 147 |
+
"author_email",
|
| 148 |
+
"description",
|
| 149 |
+
"description_content_type",
|
| 150 |
+
"download_url",
|
| 151 |
+
"home_page",
|
| 152 |
+
"license",
|
| 153 |
+
"license_expression",
|
| 154 |
+
"maintainer",
|
| 155 |
+
"maintainer_email",
|
| 156 |
+
"metadata_version",
|
| 157 |
+
"name",
|
| 158 |
+
"requires_python",
|
| 159 |
+
"summary",
|
| 160 |
+
"version",
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
_LIST_FIELDS = {
|
| 164 |
+
"classifiers",
|
| 165 |
+
"dynamic",
|
| 166 |
+
"license_files",
|
| 167 |
+
"obsoletes",
|
| 168 |
+
"obsoletes_dist",
|
| 169 |
+
"platforms",
|
| 170 |
+
"provides",
|
| 171 |
+
"provides_dist",
|
| 172 |
+
"provides_extra",
|
| 173 |
+
"requires",
|
| 174 |
+
"requires_dist",
|
| 175 |
+
"requires_external",
|
| 176 |
+
"supported_platforms",
|
| 177 |
+
"import_names",
|
| 178 |
+
"import_namespaces",
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
_DICT_FIELDS = {
|
| 182 |
+
"project_urls",
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def _parse_keywords(data: str) -> list[str]:
|
| 187 |
+
"""Split a string of comma-separated keywords into a list of keywords."""
|
| 188 |
+
return [k.strip() for k in data.split(",")]
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def _parse_project_urls(data: list[str]) -> dict[str, str]:
|
| 192 |
+
"""Parse a list of label/URL string pairings separated by a comma."""
|
| 193 |
+
urls = {}
|
| 194 |
+
for pair in data:
|
| 195 |
+
# Our logic is slightly tricky here as we want to try and do
|
| 196 |
+
# *something* reasonable with malformed data.
|
| 197 |
+
#
|
| 198 |
+
# The main thing that we have to worry about, is data that does
|
| 199 |
+
# not have a ',' at all to split the label from the Value. There
|
| 200 |
+
# isn't a singular right answer here, and we will fail validation
|
| 201 |
+
# later on (if the caller is validating) so it doesn't *really*
|
| 202 |
+
# matter, but since the missing value has to be an empty str
|
| 203 |
+
# and our return value is dict[str, str], if we let the key
|
| 204 |
+
# be the missing value, then they'd have multiple '' values that
|
| 205 |
+
# overwrite each other in a accumulating dict.
|
| 206 |
+
#
|
| 207 |
+
# The other potential issue is that it's possible to have the
|
| 208 |
+
# same label multiple times in the metadata, with no solid "right"
|
| 209 |
+
# answer with what to do in that case. As such, we'll do the only
|
| 210 |
+
# thing we can, which is treat the field as unparsable and add it
|
| 211 |
+
# to our list of unparsed fields.
|
| 212 |
+
#
|
| 213 |
+
# TODO: The spec doesn't say anything about if the keys should be
|
| 214 |
+
# considered case sensitive or not... logically they should
|
| 215 |
+
# be case-preserving and case-insensitive, but doing that
|
| 216 |
+
# would open up more cases where we might have duplicate
|
| 217 |
+
# entries.
|
| 218 |
+
label, _, url = (s.strip() for s in pair.partition(","))
|
| 219 |
+
|
| 220 |
+
if label in urls:
|
| 221 |
+
# The label already exists in our set of urls, so this field
|
| 222 |
+
# is unparsable, and we can just add the whole thing to our
|
| 223 |
+
# unparsable data and stop processing it.
|
| 224 |
+
raise KeyError("duplicate labels in project urls")
|
| 225 |
+
urls[label] = url
|
| 226 |
+
|
| 227 |
+
return urls
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def _get_payload(msg: email.message.Message, source: bytes | str) -> str:
|
| 231 |
+
"""Get the body of the message."""
|
| 232 |
+
# If our source is a str, then our caller has managed encodings for us,
|
| 233 |
+
# and we don't need to deal with it.
|
| 234 |
+
if isinstance(source, str):
|
| 235 |
+
payload = msg.get_payload()
|
| 236 |
+
assert isinstance(payload, str)
|
| 237 |
+
return payload
|
| 238 |
+
# If our source is a bytes, then we're managing the encoding and we need
|
| 239 |
+
# to deal with it.
|
| 240 |
+
else:
|
| 241 |
+
bpayload = msg.get_payload(decode=True)
|
| 242 |
+
assert isinstance(bpayload, bytes)
|
| 243 |
+
try:
|
| 244 |
+
return bpayload.decode("utf8", "strict")
|
| 245 |
+
except UnicodeDecodeError as exc:
|
| 246 |
+
raise ValueError("payload in an invalid encoding") from exc
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
# The various parse_FORMAT functions here are intended to be as lenient as
|
| 250 |
+
# possible in their parsing, while still returning a correctly typed
|
| 251 |
+
# RawMetadata.
|
| 252 |
+
#
|
| 253 |
+
# To aid in this, we also generally want to do as little touching of the
|
| 254 |
+
# data as possible, except where there are possibly some historic holdovers
|
| 255 |
+
# that make valid data awkward to work with.
|
| 256 |
+
#
|
| 257 |
+
# While this is a lower level, intermediate format than our ``Metadata``
|
| 258 |
+
# class, some light touch ups can make a massive difference in usability.
|
| 259 |
+
|
| 260 |
+
# Map METADATA fields to RawMetadata.
|
| 261 |
+
_EMAIL_TO_RAW_MAPPING = {
|
| 262 |
+
"author": "author",
|
| 263 |
+
"author-email": "author_email",
|
| 264 |
+
"classifier": "classifiers",
|
| 265 |
+
"description": "description",
|
| 266 |
+
"description-content-type": "description_content_type",
|
| 267 |
+
"download-url": "download_url",
|
| 268 |
+
"dynamic": "dynamic",
|
| 269 |
+
"home-page": "home_page",
|
| 270 |
+
"import-name": "import_names",
|
| 271 |
+
"import-namespace": "import_namespaces",
|
| 272 |
+
"keywords": "keywords",
|
| 273 |
+
"license": "license",
|
| 274 |
+
"license-expression": "license_expression",
|
| 275 |
+
"license-file": "license_files",
|
| 276 |
+
"maintainer": "maintainer",
|
| 277 |
+
"maintainer-email": "maintainer_email",
|
| 278 |
+
"metadata-version": "metadata_version",
|
| 279 |
+
"name": "name",
|
| 280 |
+
"obsoletes": "obsoletes",
|
| 281 |
+
"obsoletes-dist": "obsoletes_dist",
|
| 282 |
+
"platform": "platforms",
|
| 283 |
+
"project-url": "project_urls",
|
| 284 |
+
"provides": "provides",
|
| 285 |
+
"provides-dist": "provides_dist",
|
| 286 |
+
"provides-extra": "provides_extra",
|
| 287 |
+
"requires": "requires",
|
| 288 |
+
"requires-dist": "requires_dist",
|
| 289 |
+
"requires-external": "requires_external",
|
| 290 |
+
"requires-python": "requires_python",
|
| 291 |
+
"summary": "summary",
|
| 292 |
+
"supported-platform": "supported_platforms",
|
| 293 |
+
"version": "version",
|
| 294 |
+
}
|
| 295 |
+
_RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()}
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
# This class is for writing RFC822 messages
|
| 299 |
+
class RFC822Policy(email.policy.EmailPolicy):
|
| 300 |
+
"""
|
| 301 |
+
This is :class:`email.policy.EmailPolicy`, but with a simple ``header_store_parse``
|
| 302 |
+
implementation that handles multi-line values, and some nice defaults.
|
| 303 |
+
"""
|
| 304 |
+
|
| 305 |
+
utf8 = True
|
| 306 |
+
mangle_from_ = False
|
| 307 |
+
max_line_length = 0
|
| 308 |
+
|
| 309 |
+
def header_store_parse(self, name: str, value: str) -> tuple[str, str]:
|
| 310 |
+
size = len(name) + 2
|
| 311 |
+
value = value.replace("\n", "\n" + " " * size)
|
| 312 |
+
return (name, value)
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
# This class is for writing RFC822 messages
|
| 316 |
+
class RFC822Message(email.message.EmailMessage):
|
| 317 |
+
"""
|
| 318 |
+
This is :class:`email.message.EmailMessage` with two small changes: it defaults to
|
| 319 |
+
our `RFC822Policy`, and it correctly writes unicode when being called
|
| 320 |
+
with `bytes()`.
|
| 321 |
+
"""
|
| 322 |
+
|
| 323 |
+
def __init__(self) -> None:
|
| 324 |
+
super().__init__(policy=RFC822Policy())
|
| 325 |
+
|
| 326 |
+
def as_bytes(
|
| 327 |
+
self, unixfrom: bool = False, policy: email.policy.Policy | None = None
|
| 328 |
+
) -> bytes:
|
| 329 |
+
"""
|
| 330 |
+
Return the bytes representation of the message.
|
| 331 |
+
|
| 332 |
+
This handles unicode encoding.
|
| 333 |
+
"""
|
| 334 |
+
return self.as_string(unixfrom, policy=policy).encode("utf-8")
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
|
| 338 |
+
"""Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``).
|
| 339 |
+
|
| 340 |
+
This function returns a two-item tuple of dicts. The first dict is of
|
| 341 |
+
recognized fields from the core metadata specification. Fields that can be
|
| 342 |
+
parsed and translated into Python's built-in types are converted
|
| 343 |
+
appropriately. All other fields are left as-is. Fields that are allowed to
|
| 344 |
+
appear multiple times are stored as lists.
|
| 345 |
+
|
| 346 |
+
The second dict contains all other fields from the metadata. This includes
|
| 347 |
+
any unrecognized fields. It also includes any fields which are expected to
|
| 348 |
+
be parsed into a built-in type but were not formatted appropriately. Finally,
|
| 349 |
+
any fields that are expected to appear only once but are repeated are
|
| 350 |
+
included in this dict.
|
| 351 |
+
|
| 352 |
+
"""
|
| 353 |
+
raw: dict[str, str | list[str] | dict[str, str]] = {}
|
| 354 |
+
unparsed: dict[str, list[str]] = {}
|
| 355 |
+
|
| 356 |
+
if isinstance(data, str):
|
| 357 |
+
parsed = email.parser.Parser(policy=email.policy.compat32).parsestr(data)
|
| 358 |
+
else:
|
| 359 |
+
parsed = email.parser.BytesParser(policy=email.policy.compat32).parsebytes(data)
|
| 360 |
+
|
| 361 |
+
# We have to wrap parsed.keys() in a set, because in the case of multiple
|
| 362 |
+
# values for a key (a list), the key will appear multiple times in the
|
| 363 |
+
# list of keys, but we're avoiding that by using get_all().
|
| 364 |
+
for name_with_case in frozenset(parsed.keys()):
|
| 365 |
+
# Header names in RFC are case insensitive, so we'll normalize to all
|
| 366 |
+
# lower case to make comparisons easier.
|
| 367 |
+
name = name_with_case.lower()
|
| 368 |
+
|
| 369 |
+
# We use get_all() here, even for fields that aren't multiple use,
|
| 370 |
+
# because otherwise someone could have e.g. two Name fields, and we
|
| 371 |
+
# would just silently ignore it rather than doing something about it.
|
| 372 |
+
headers = parsed.get_all(name) or []
|
| 373 |
+
|
| 374 |
+
# The way the email module works when parsing bytes is that it
|
| 375 |
+
# unconditionally decodes the bytes as ascii using the surrogateescape
|
| 376 |
+
# handler. When you pull that data back out (such as with get_all() ),
|
| 377 |
+
# it looks to see if the str has any surrogate escapes, and if it does
|
| 378 |
+
# it wraps it in a Header object instead of returning the string.
|
| 379 |
+
#
|
| 380 |
+
# As such, we'll look for those Header objects, and fix up the encoding.
|
| 381 |
+
value = []
|
| 382 |
+
# Flag if we have run into any issues processing the headers, thus
|
| 383 |
+
# signalling that the data belongs in 'unparsed'.
|
| 384 |
+
valid_encoding = True
|
| 385 |
+
for h in headers:
|
| 386 |
+
# It's unclear if this can return more types than just a Header or
|
| 387 |
+
# a str, so we'll just assert here to make sure.
|
| 388 |
+
assert isinstance(h, (email.header.Header, str))
|
| 389 |
+
|
| 390 |
+
# If it's a header object, we need to do our little dance to get
|
| 391 |
+
# the real data out of it. In cases where there is invalid data
|
| 392 |
+
# we're going to end up with mojibake, but there's no obvious, good
|
| 393 |
+
# way around that without reimplementing parts of the Header object
|
| 394 |
+
# ourselves.
|
| 395 |
+
#
|
| 396 |
+
# That should be fine since, if mojibacked happens, this key is
|
| 397 |
+
# going into the unparsed dict anyways.
|
| 398 |
+
if isinstance(h, email.header.Header):
|
| 399 |
+
# The Header object stores it's data as chunks, and each chunk
|
| 400 |
+
# can be independently encoded, so we'll need to check each
|
| 401 |
+
# of them.
|
| 402 |
+
chunks: list[tuple[bytes, str | None]] = []
|
| 403 |
+
for binary, _encoding in email.header.decode_header(h):
|
| 404 |
+
try:
|
| 405 |
+
binary.decode("utf8", "strict")
|
| 406 |
+
except UnicodeDecodeError:
|
| 407 |
+
# Enable mojibake.
|
| 408 |
+
encoding = "latin1"
|
| 409 |
+
valid_encoding = False
|
| 410 |
+
else:
|
| 411 |
+
encoding = "utf8"
|
| 412 |
+
chunks.append((binary, encoding))
|
| 413 |
+
|
| 414 |
+
# Turn our chunks back into a Header object, then let that
|
| 415 |
+
# Header object do the right thing to turn them into a
|
| 416 |
+
# string for us.
|
| 417 |
+
value.append(str(email.header.make_header(chunks)))
|
| 418 |
+
# This is already a string, so just add it.
|
| 419 |
+
else:
|
| 420 |
+
value.append(h)
|
| 421 |
+
|
| 422 |
+
# We've processed all of our values to get them into a list of str,
|
| 423 |
+
# but we may have mojibake data, in which case this is an unparsed
|
| 424 |
+
# field.
|
| 425 |
+
if not valid_encoding:
|
| 426 |
+
unparsed[name] = value
|
| 427 |
+
continue
|
| 428 |
+
|
| 429 |
+
raw_name = _EMAIL_TO_RAW_MAPPING.get(name)
|
| 430 |
+
if raw_name is None:
|
| 431 |
+
# This is a bit of a weird situation, we've encountered a key that
|
| 432 |
+
# we don't know what it means, so we don't know whether it's meant
|
| 433 |
+
# to be a list or not.
|
| 434 |
+
#
|
| 435 |
+
# Since we can't really tell one way or another, we'll just leave it
|
| 436 |
+
# as a list, even though it may be a single item list, because that's
|
| 437 |
+
# what makes the most sense for email headers.
|
| 438 |
+
unparsed[name] = value
|
| 439 |
+
continue
|
| 440 |
+
|
| 441 |
+
# If this is one of our string fields, then we'll check to see if our
|
| 442 |
+
# value is a list of a single item. If it is then we'll assume that
|
| 443 |
+
# it was emitted as a single string, and unwrap the str from inside
|
| 444 |
+
# the list.
|
| 445 |
+
#
|
| 446 |
+
# If it's any other kind of data, then we haven't the faintest clue
|
| 447 |
+
# what we should parse it as, and we have to just add it to our list
|
| 448 |
+
# of unparsed stuff.
|
| 449 |
+
if raw_name in _STRING_FIELDS and len(value) == 1:
|
| 450 |
+
raw[raw_name] = value[0]
|
| 451 |
+
# If this is import_names, we need to special case the empty field
|
| 452 |
+
# case, which converts to an empty list instead of None. We can't let
|
| 453 |
+
# the empty case slip through, as it will fail validation.
|
| 454 |
+
elif raw_name == "import_names" and value == [""]:
|
| 455 |
+
raw[raw_name] = []
|
| 456 |
+
# If this is one of our list of string fields, then we can just assign
|
| 457 |
+
# the value, since email *only* has strings, and our get_all() call
|
| 458 |
+
# above ensures that this is a list.
|
| 459 |
+
elif raw_name in _LIST_FIELDS:
|
| 460 |
+
raw[raw_name] = value
|
| 461 |
+
# Special Case: Keywords
|
| 462 |
+
# The keywords field is implemented in the metadata spec as a str,
|
| 463 |
+
# but it conceptually is a list of strings, and is serialized using
|
| 464 |
+
# ", ".join(keywords), so we'll do some light data massaging to turn
|
| 465 |
+
# this into what it logically is.
|
| 466 |
+
elif raw_name == "keywords" and len(value) == 1:
|
| 467 |
+
raw[raw_name] = _parse_keywords(value[0])
|
| 468 |
+
# Special Case: Project-URL
|
| 469 |
+
# The project urls is implemented in the metadata spec as a list of
|
| 470 |
+
# specially-formatted strings that represent a key and a value, which
|
| 471 |
+
# is fundamentally a mapping, however the email format doesn't support
|
| 472 |
+
# mappings in a sane way, so it was crammed into a list of strings
|
| 473 |
+
# instead.
|
| 474 |
+
#
|
| 475 |
+
# We will do a little light data massaging to turn this into a map as
|
| 476 |
+
# it logically should be.
|
| 477 |
+
elif raw_name == "project_urls":
|
| 478 |
+
try:
|
| 479 |
+
raw[raw_name] = _parse_project_urls(value)
|
| 480 |
+
except KeyError:
|
| 481 |
+
unparsed[name] = value
|
| 482 |
+
# Nothing that we've done has managed to parse this, so it'll just
|
| 483 |
+
# throw it in our unparsable data and move on.
|
| 484 |
+
else:
|
| 485 |
+
unparsed[name] = value
|
| 486 |
+
|
| 487 |
+
# We need to support getting the Description from the message payload in
|
| 488 |
+
# addition to getting it from the the headers. This does mean, though, there
|
| 489 |
+
# is the possibility of it being set both ways, in which case we put both
|
| 490 |
+
# in 'unparsed' since we don't know which is right.
|
| 491 |
+
try:
|
| 492 |
+
payload = _get_payload(parsed, data)
|
| 493 |
+
except ValueError:
|
| 494 |
+
unparsed.setdefault("description", []).append(
|
| 495 |
+
parsed.get_payload(decode=isinstance(data, bytes)) # type: ignore[call-overload]
|
| 496 |
+
)
|
| 497 |
+
else:
|
| 498 |
+
if payload:
|
| 499 |
+
# Check to see if we've already got a description, if so then both
|
| 500 |
+
# it, and this body move to unparsable.
|
| 501 |
+
if "description" in raw:
|
| 502 |
+
description_header = cast("str", raw.pop("description"))
|
| 503 |
+
unparsed.setdefault("description", []).extend(
|
| 504 |
+
[description_header, payload]
|
| 505 |
+
)
|
| 506 |
+
elif "description" in unparsed:
|
| 507 |
+
unparsed["description"].append(payload)
|
| 508 |
+
else:
|
| 509 |
+
raw["description"] = payload
|
| 510 |
+
|
| 511 |
+
# We need to cast our `raw` to a metadata, because a TypedDict only support
|
| 512 |
+
# literal key names, but we're computing our key names on purpose, but the
|
| 513 |
+
# way this function is implemented, our `TypedDict` can only have valid key
|
| 514 |
+
# names.
|
| 515 |
+
return cast("RawMetadata", raw), unparsed
|
| 516 |
+
|
| 517 |
+
|
| 518 |
+
_NOT_FOUND = object()
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
# Keep the two values in sync.
|
| 522 |
+
_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4", "2.5"]
|
| 523 |
+
_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4", "2.5"]
|
| 524 |
+
|
| 525 |
+
_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"])
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
class _Validator(Generic[T]):
|
| 529 |
+
"""Validate a metadata field.
|
| 530 |
+
|
| 531 |
+
All _process_*() methods correspond to a core metadata field. The method is
|
| 532 |
+
called with the field's raw value. If the raw value is valid it is returned
|
| 533 |
+
in its "enriched" form (e.g. ``version.Version`` for the ``Version`` field).
|
| 534 |
+
If the raw value is invalid, :exc:`InvalidMetadata` is raised (with a cause
|
| 535 |
+
as appropriate).
|
| 536 |
+
"""
|
| 537 |
+
|
| 538 |
+
name: str
|
| 539 |
+
raw_name: str
|
| 540 |
+
added: _MetadataVersion
|
| 541 |
+
|
| 542 |
+
def __init__(
|
| 543 |
+
self,
|
| 544 |
+
*,
|
| 545 |
+
added: _MetadataVersion = "1.0",
|
| 546 |
+
) -> None:
|
| 547 |
+
self.added = added
|
| 548 |
+
|
| 549 |
+
def __set_name__(self, _owner: Metadata, name: str) -> None:
|
| 550 |
+
self.name = name
|
| 551 |
+
self.raw_name = _RAW_TO_EMAIL_MAPPING[name]
|
| 552 |
+
|
| 553 |
+
def __get__(self, instance: Metadata, _owner: type[Metadata]) -> T:
|
| 554 |
+
# With Python 3.8, the caching can be replaced with functools.cached_property().
|
| 555 |
+
# No need to check the cache as attribute lookup will resolve into the
|
| 556 |
+
# instance's __dict__ before __get__ is called.
|
| 557 |
+
cache = instance.__dict__
|
| 558 |
+
value = instance._raw.get(self.name)
|
| 559 |
+
|
| 560 |
+
# To make the _process_* methods easier, we'll check if the value is None
|
| 561 |
+
# and if this field is NOT a required attribute, and if both of those
|
| 562 |
+
# things are true, we'll skip the the converter. This will mean that the
|
| 563 |
+
# converters never have to deal with the None union.
|
| 564 |
+
if self.name in _REQUIRED_ATTRS or value is not None:
|
| 565 |
+
try:
|
| 566 |
+
converter: Callable[[Any], T] = getattr(self, f"_process_{self.name}")
|
| 567 |
+
except AttributeError:
|
| 568 |
+
pass
|
| 569 |
+
else:
|
| 570 |
+
value = converter(value)
|
| 571 |
+
|
| 572 |
+
cache[self.name] = value
|
| 573 |
+
try:
|
| 574 |
+
del instance._raw[self.name] # type: ignore[misc]
|
| 575 |
+
except KeyError:
|
| 576 |
+
pass
|
| 577 |
+
|
| 578 |
+
return cast("T", value)
|
| 579 |
+
|
| 580 |
+
def _invalid_metadata(
|
| 581 |
+
self, msg: str, cause: Exception | None = None
|
| 582 |
+
) -> InvalidMetadata:
|
| 583 |
+
exc = InvalidMetadata(
|
| 584 |
+
self.raw_name, msg.format_map({"field": repr(self.raw_name)})
|
| 585 |
+
)
|
| 586 |
+
exc.__cause__ = cause
|
| 587 |
+
return exc
|
| 588 |
+
|
| 589 |
+
def _process_metadata_version(self, value: str) -> _MetadataVersion:
|
| 590 |
+
# Implicitly makes Metadata-Version required.
|
| 591 |
+
if value not in _VALID_METADATA_VERSIONS:
|
| 592 |
+
raise self._invalid_metadata(f"{value!r} is not a valid metadata version")
|
| 593 |
+
return cast("_MetadataVersion", value)
|
| 594 |
+
|
| 595 |
+
def _process_name(self, value: str) -> str:
|
| 596 |
+
if not value:
|
| 597 |
+
raise self._invalid_metadata("{field} is a required field")
|
| 598 |
+
# Validate the name as a side-effect.
|
| 599 |
+
try:
|
| 600 |
+
utils.canonicalize_name(value, validate=True)
|
| 601 |
+
except utils.InvalidName as exc:
|
| 602 |
+
raise self._invalid_metadata(
|
| 603 |
+
f"{value!r} is invalid for {{field}}", cause=exc
|
| 604 |
+
) from exc
|
| 605 |
+
else:
|
| 606 |
+
return value
|
| 607 |
+
|
| 608 |
+
def _process_version(self, value: str) -> version_module.Version:
|
| 609 |
+
if not value:
|
| 610 |
+
raise self._invalid_metadata("{field} is a required field")
|
| 611 |
+
try:
|
| 612 |
+
return version_module.parse(value)
|
| 613 |
+
except version_module.InvalidVersion as exc:
|
| 614 |
+
raise self._invalid_metadata(
|
| 615 |
+
f"{value!r} is invalid for {{field}}", cause=exc
|
| 616 |
+
) from exc
|
| 617 |
+
|
| 618 |
+
def _process_summary(self, value: str) -> str:
|
| 619 |
+
"""Check the field contains no newlines."""
|
| 620 |
+
if "\n" in value:
|
| 621 |
+
raise self._invalid_metadata("{field} must be a single line")
|
| 622 |
+
return value
|
| 623 |
+
|
| 624 |
+
def _process_description_content_type(self, value: str) -> str:
|
| 625 |
+
content_types = {"text/plain", "text/x-rst", "text/markdown"}
|
| 626 |
+
message = email.message.EmailMessage()
|
| 627 |
+
message["content-type"] = value
|
| 628 |
+
|
| 629 |
+
content_type, parameters = (
|
| 630 |
+
# Defaults to `text/plain` if parsing failed.
|
| 631 |
+
message.get_content_type().lower(),
|
| 632 |
+
message["content-type"].params,
|
| 633 |
+
)
|
| 634 |
+
# Check if content-type is valid or defaulted to `text/plain` and thus was
|
| 635 |
+
# not parseable.
|
| 636 |
+
if content_type not in content_types or content_type not in value.lower():
|
| 637 |
+
raise self._invalid_metadata(
|
| 638 |
+
f"{{field}} must be one of {list(content_types)}, not {value!r}"
|
| 639 |
+
)
|
| 640 |
+
|
| 641 |
+
charset = parameters.get("charset", "UTF-8")
|
| 642 |
+
if charset != "UTF-8":
|
| 643 |
+
raise self._invalid_metadata(
|
| 644 |
+
f"{{field}} can only specify the UTF-8 charset, not {list(charset)}"
|
| 645 |
+
)
|
| 646 |
+
|
| 647 |
+
markdown_variants = {"GFM", "CommonMark"}
|
| 648 |
+
variant = parameters.get("variant", "GFM") # Use an acceptable default.
|
| 649 |
+
if content_type == "text/markdown" and variant not in markdown_variants:
|
| 650 |
+
raise self._invalid_metadata(
|
| 651 |
+
f"valid Markdown variants for {{field}} are {list(markdown_variants)}, "
|
| 652 |
+
f"not {variant!r}",
|
| 653 |
+
)
|
| 654 |
+
return value
|
| 655 |
+
|
| 656 |
+
def _process_dynamic(self, value: list[str]) -> list[str]:
|
| 657 |
+
for dynamic_field in map(str.lower, value):
|
| 658 |
+
if dynamic_field in {"name", "version", "metadata-version"}:
|
| 659 |
+
raise self._invalid_metadata(
|
| 660 |
+
f"{dynamic_field!r} is not allowed as a dynamic field"
|
| 661 |
+
)
|
| 662 |
+
elif dynamic_field not in _EMAIL_TO_RAW_MAPPING:
|
| 663 |
+
raise self._invalid_metadata(
|
| 664 |
+
f"{dynamic_field!r} is not a valid dynamic field"
|
| 665 |
+
)
|
| 666 |
+
return list(map(str.lower, value))
|
| 667 |
+
|
| 668 |
+
def _process_provides_extra(
|
| 669 |
+
self,
|
| 670 |
+
value: list[str],
|
| 671 |
+
) -> list[utils.NormalizedName]:
|
| 672 |
+
normalized_names = []
|
| 673 |
+
try:
|
| 674 |
+
for name in value:
|
| 675 |
+
normalized_names.append(utils.canonicalize_name(name, validate=True))
|
| 676 |
+
except utils.InvalidName as exc:
|
| 677 |
+
raise self._invalid_metadata(
|
| 678 |
+
f"{name!r} is invalid for {{field}}", cause=exc
|
| 679 |
+
) from exc
|
| 680 |
+
else:
|
| 681 |
+
return normalized_names
|
| 682 |
+
|
| 683 |
+
def _process_requires_python(self, value: str) -> specifiers.SpecifierSet:
|
| 684 |
+
try:
|
| 685 |
+
return specifiers.SpecifierSet(value)
|
| 686 |
+
except specifiers.InvalidSpecifier as exc:
|
| 687 |
+
raise self._invalid_metadata(
|
| 688 |
+
f"{value!r} is invalid for {{field}}", cause=exc
|
| 689 |
+
) from exc
|
| 690 |
+
|
| 691 |
+
def _process_requires_dist(
|
| 692 |
+
self,
|
| 693 |
+
value: list[str],
|
| 694 |
+
) -> list[requirements.Requirement]:
|
| 695 |
+
reqs = []
|
| 696 |
+
try:
|
| 697 |
+
for req in value:
|
| 698 |
+
reqs.append(requirements.Requirement(req))
|
| 699 |
+
except requirements.InvalidRequirement as exc:
|
| 700 |
+
raise self._invalid_metadata(
|
| 701 |
+
f"{req!r} is invalid for {{field}}", cause=exc
|
| 702 |
+
) from exc
|
| 703 |
+
else:
|
| 704 |
+
return reqs
|
| 705 |
+
|
| 706 |
+
def _process_license_expression(self, value: str) -> NormalizedLicenseExpression:
|
| 707 |
+
try:
|
| 708 |
+
return licenses.canonicalize_license_expression(value)
|
| 709 |
+
except ValueError as exc:
|
| 710 |
+
raise self._invalid_metadata(
|
| 711 |
+
f"{value!r} is invalid for {{field}}", cause=exc
|
| 712 |
+
) from exc
|
| 713 |
+
|
| 714 |
+
def _process_license_files(self, value: list[str]) -> list[str]:
|
| 715 |
+
paths = []
|
| 716 |
+
for path in value:
|
| 717 |
+
if ".." in path:
|
| 718 |
+
raise self._invalid_metadata(
|
| 719 |
+
f"{path!r} is invalid for {{field}}, "
|
| 720 |
+
"parent directory indicators are not allowed"
|
| 721 |
+
)
|
| 722 |
+
if "*" in path:
|
| 723 |
+
raise self._invalid_metadata(
|
| 724 |
+
f"{path!r} is invalid for {{field}}, paths must be resolved"
|
| 725 |
+
)
|
| 726 |
+
if (
|
| 727 |
+
pathlib.PurePosixPath(path).is_absolute()
|
| 728 |
+
or pathlib.PureWindowsPath(path).is_absolute()
|
| 729 |
+
):
|
| 730 |
+
raise self._invalid_metadata(
|
| 731 |
+
f"{path!r} is invalid for {{field}}, paths must be relative"
|
| 732 |
+
)
|
| 733 |
+
if pathlib.PureWindowsPath(path).as_posix() != path:
|
| 734 |
+
raise self._invalid_metadata(
|
| 735 |
+
f"{path!r} is invalid for {{field}}, paths must use '/' delimiter"
|
| 736 |
+
)
|
| 737 |
+
paths.append(path)
|
| 738 |
+
return paths
|
| 739 |
+
|
| 740 |
+
def _process_import_names(self, value: list[str]) -> list[str]:
|
| 741 |
+
for import_name in value:
|
| 742 |
+
name, semicolon, private = import_name.partition(";")
|
| 743 |
+
name = name.rstrip()
|
| 744 |
+
for identifier in name.split("."):
|
| 745 |
+
if not identifier.isidentifier():
|
| 746 |
+
raise self._invalid_metadata(
|
| 747 |
+
f"{name!r} is invalid for {{field}}; "
|
| 748 |
+
f"{identifier!r} is not a valid identifier"
|
| 749 |
+
)
|
| 750 |
+
elif keyword.iskeyword(identifier):
|
| 751 |
+
raise self._invalid_metadata(
|
| 752 |
+
f"{name!r} is invalid for {{field}}; "
|
| 753 |
+
f"{identifier!r} is a keyword"
|
| 754 |
+
)
|
| 755 |
+
if semicolon and private.lstrip() != "private":
|
| 756 |
+
raise self._invalid_metadata(
|
| 757 |
+
f"{import_name!r} is invalid for {{field}}; "
|
| 758 |
+
"the only valid option is 'private'"
|
| 759 |
+
)
|
| 760 |
+
return value
|
| 761 |
+
|
| 762 |
+
_process_import_namespaces = _process_import_names
|
| 763 |
+
|
| 764 |
+
|
| 765 |
+
class Metadata:
|
| 766 |
+
"""Representation of distribution metadata.
|
| 767 |
+
|
| 768 |
+
Compared to :class:`RawMetadata`, this class provides objects representing
|
| 769 |
+
metadata fields instead of only using built-in types. Any invalid metadata
|
| 770 |
+
will cause :exc:`InvalidMetadata` to be raised (with a
|
| 771 |
+
:py:attr:`~BaseException.__cause__` attribute as appropriate).
|
| 772 |
+
"""
|
| 773 |
+
|
| 774 |
+
_raw: RawMetadata
|
| 775 |
+
|
| 776 |
+
@classmethod
|
| 777 |
+
def from_raw(cls, data: RawMetadata, *, validate: bool = True) -> Metadata:
|
| 778 |
+
"""Create an instance from :class:`RawMetadata`.
|
| 779 |
+
|
| 780 |
+
If *validate* is true, all metadata will be validated. All exceptions
|
| 781 |
+
related to validation will be gathered and raised as an :class:`ExceptionGroup`.
|
| 782 |
+
"""
|
| 783 |
+
ins = cls()
|
| 784 |
+
ins._raw = data.copy() # Mutations occur due to caching enriched values.
|
| 785 |
+
|
| 786 |
+
if validate:
|
| 787 |
+
exceptions: list[Exception] = []
|
| 788 |
+
try:
|
| 789 |
+
metadata_version = ins.metadata_version
|
| 790 |
+
metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version)
|
| 791 |
+
except InvalidMetadata as metadata_version_exc:
|
| 792 |
+
exceptions.append(metadata_version_exc)
|
| 793 |
+
metadata_version = None
|
| 794 |
+
|
| 795 |
+
# Make sure to check for the fields that are present, the required
|
| 796 |
+
# fields (so their absence can be reported).
|
| 797 |
+
fields_to_check = frozenset(ins._raw) | _REQUIRED_ATTRS
|
| 798 |
+
# Remove fields that have already been checked.
|
| 799 |
+
fields_to_check -= {"metadata_version"}
|
| 800 |
+
|
| 801 |
+
for key in fields_to_check:
|
| 802 |
+
try:
|
| 803 |
+
if metadata_version:
|
| 804 |
+
# Can't use getattr() as that triggers descriptor protocol which
|
| 805 |
+
# will fail due to no value for the instance argument.
|
| 806 |
+
try:
|
| 807 |
+
field_metadata_version = cls.__dict__[key].added
|
| 808 |
+
except KeyError:
|
| 809 |
+
exc = InvalidMetadata(key, f"unrecognized field: {key!r}")
|
| 810 |
+
exceptions.append(exc)
|
| 811 |
+
continue
|
| 812 |
+
field_age = _VALID_METADATA_VERSIONS.index(
|
| 813 |
+
field_metadata_version
|
| 814 |
+
)
|
| 815 |
+
if field_age > metadata_age:
|
| 816 |
+
field = _RAW_TO_EMAIL_MAPPING[key]
|
| 817 |
+
exc = InvalidMetadata(
|
| 818 |
+
field,
|
| 819 |
+
f"{field} introduced in metadata version "
|
| 820 |
+
f"{field_metadata_version}, not {metadata_version}",
|
| 821 |
+
)
|
| 822 |
+
exceptions.append(exc)
|
| 823 |
+
continue
|
| 824 |
+
getattr(ins, key)
|
| 825 |
+
except InvalidMetadata as exc:
|
| 826 |
+
exceptions.append(exc)
|
| 827 |
+
|
| 828 |
+
if exceptions:
|
| 829 |
+
raise ExceptionGroup("invalid metadata", exceptions)
|
| 830 |
+
|
| 831 |
+
return ins
|
| 832 |
+
|
| 833 |
+
@classmethod
|
| 834 |
+
def from_email(cls, data: bytes | str, *, validate: bool = True) -> Metadata:
|
| 835 |
+
"""Parse metadata from email headers.
|
| 836 |
+
|
| 837 |
+
If *validate* is true, the metadata will be validated. All exceptions
|
| 838 |
+
related to validation will be gathered and raised as an :class:`ExceptionGroup`.
|
| 839 |
+
"""
|
| 840 |
+
raw, unparsed = parse_email(data)
|
| 841 |
+
|
| 842 |
+
if validate:
|
| 843 |
+
exceptions: list[Exception] = []
|
| 844 |
+
for unparsed_key in unparsed:
|
| 845 |
+
if unparsed_key in _EMAIL_TO_RAW_MAPPING:
|
| 846 |
+
message = f"{unparsed_key!r} has invalid data"
|
| 847 |
+
else:
|
| 848 |
+
message = f"unrecognized field: {unparsed_key!r}"
|
| 849 |
+
exceptions.append(InvalidMetadata(unparsed_key, message))
|
| 850 |
+
|
| 851 |
+
if exceptions:
|
| 852 |
+
raise ExceptionGroup("unparsed", exceptions)
|
| 853 |
+
|
| 854 |
+
try:
|
| 855 |
+
return cls.from_raw(raw, validate=validate)
|
| 856 |
+
except ExceptionGroup as exc_group:
|
| 857 |
+
raise ExceptionGroup(
|
| 858 |
+
"invalid or unparsed metadata", exc_group.exceptions
|
| 859 |
+
) from None
|
| 860 |
+
|
| 861 |
+
metadata_version: _Validator[_MetadataVersion] = _Validator()
|
| 862 |
+
""":external:ref:`core-metadata-metadata-version`
|
| 863 |
+
(required; validated to be a valid metadata version)"""
|
| 864 |
+
# `name` is not normalized/typed to NormalizedName so as to provide access to
|
| 865 |
+
# the original/raw name.
|
| 866 |
+
name: _Validator[str] = _Validator()
|
| 867 |
+
""":external:ref:`core-metadata-name`
|
| 868 |
+
(required; validated using :func:`~packaging.utils.canonicalize_name` and its
|
| 869 |
+
*validate* parameter)"""
|
| 870 |
+
version: _Validator[version_module.Version] = _Validator()
|
| 871 |
+
""":external:ref:`core-metadata-version` (required)"""
|
| 872 |
+
dynamic: _Validator[list[str] | None] = _Validator(
|
| 873 |
+
added="2.2",
|
| 874 |
+
)
|
| 875 |
+
""":external:ref:`core-metadata-dynamic`
|
| 876 |
+
(validated against core metadata field names and lowercased)"""
|
| 877 |
+
platforms: _Validator[list[str] | None] = _Validator()
|
| 878 |
+
""":external:ref:`core-metadata-platform`"""
|
| 879 |
+
supported_platforms: _Validator[list[str] | None] = _Validator(added="1.1")
|
| 880 |
+
""":external:ref:`core-metadata-supported-platform`"""
|
| 881 |
+
summary: _Validator[str | None] = _Validator()
|
| 882 |
+
""":external:ref:`core-metadata-summary` (validated to contain no newlines)"""
|
| 883 |
+
description: _Validator[str | None] = _Validator() # TODO 2.1: can be in body
|
| 884 |
+
""":external:ref:`core-metadata-description`"""
|
| 885 |
+
description_content_type: _Validator[str | None] = _Validator(added="2.1")
|
| 886 |
+
""":external:ref:`core-metadata-description-content-type` (validated)"""
|
| 887 |
+
keywords: _Validator[list[str] | None] = _Validator()
|
| 888 |
+
""":external:ref:`core-metadata-keywords`"""
|
| 889 |
+
home_page: _Validator[str | None] = _Validator()
|
| 890 |
+
""":external:ref:`core-metadata-home-page`"""
|
| 891 |
+
download_url: _Validator[str | None] = _Validator(added="1.1")
|
| 892 |
+
""":external:ref:`core-metadata-download-url`"""
|
| 893 |
+
author: _Validator[str | None] = _Validator()
|
| 894 |
+
""":external:ref:`core-metadata-author`"""
|
| 895 |
+
author_email: _Validator[str | None] = _Validator()
|
| 896 |
+
""":external:ref:`core-metadata-author-email`"""
|
| 897 |
+
maintainer: _Validator[str | None] = _Validator(added="1.2")
|
| 898 |
+
""":external:ref:`core-metadata-maintainer`"""
|
| 899 |
+
maintainer_email: _Validator[str | None] = _Validator(added="1.2")
|
| 900 |
+
""":external:ref:`core-metadata-maintainer-email`"""
|
| 901 |
+
license: _Validator[str | None] = _Validator()
|
| 902 |
+
""":external:ref:`core-metadata-license`"""
|
| 903 |
+
license_expression: _Validator[NormalizedLicenseExpression | None] = _Validator(
|
| 904 |
+
added="2.4"
|
| 905 |
+
)
|
| 906 |
+
""":external:ref:`core-metadata-license-expression`"""
|
| 907 |
+
license_files: _Validator[list[str] | None] = _Validator(added="2.4")
|
| 908 |
+
""":external:ref:`core-metadata-license-file`"""
|
| 909 |
+
classifiers: _Validator[list[str] | None] = _Validator(added="1.1")
|
| 910 |
+
""":external:ref:`core-metadata-classifier`"""
|
| 911 |
+
requires_dist: _Validator[list[requirements.Requirement] | None] = _Validator(
|
| 912 |
+
added="1.2"
|
| 913 |
+
)
|
| 914 |
+
""":external:ref:`core-metadata-requires-dist`"""
|
| 915 |
+
requires_python: _Validator[specifiers.SpecifierSet | None] = _Validator(
|
| 916 |
+
added="1.2"
|
| 917 |
+
)
|
| 918 |
+
""":external:ref:`core-metadata-requires-python`"""
|
| 919 |
+
# Because `Requires-External` allows for non-PEP 440 version specifiers, we
|
| 920 |
+
# don't do any processing on the values.
|
| 921 |
+
requires_external: _Validator[list[str] | None] = _Validator(added="1.2")
|
| 922 |
+
""":external:ref:`core-metadata-requires-external`"""
|
| 923 |
+
project_urls: _Validator[dict[str, str] | None] = _Validator(added="1.2")
|
| 924 |
+
""":external:ref:`core-metadata-project-url`"""
|
| 925 |
+
# PEP 685 lets us raise an error if an extra doesn't pass `Name` validation
|
| 926 |
+
# regardless of metadata version.
|
| 927 |
+
provides_extra: _Validator[list[utils.NormalizedName] | None] = _Validator(
|
| 928 |
+
added="2.1",
|
| 929 |
+
)
|
| 930 |
+
""":external:ref:`core-metadata-provides-extra`"""
|
| 931 |
+
provides_dist: _Validator[list[str] | None] = _Validator(added="1.2")
|
| 932 |
+
""":external:ref:`core-metadata-provides-dist`"""
|
| 933 |
+
obsoletes_dist: _Validator[list[str] | None] = _Validator(added="1.2")
|
| 934 |
+
""":external:ref:`core-metadata-obsoletes-dist`"""
|
| 935 |
+
import_names: _Validator[list[str] | None] = _Validator(added="2.5")
|
| 936 |
+
""":external:ref:`core-metadata-import-name`"""
|
| 937 |
+
import_namespaces: _Validator[list[str] | None] = _Validator(added="2.5")
|
| 938 |
+
""":external:ref:`core-metadata-import-namespace`"""
|
| 939 |
+
requires: _Validator[list[str] | None] = _Validator(added="1.1")
|
| 940 |
+
"""``Requires`` (deprecated)"""
|
| 941 |
+
provides: _Validator[list[str] | None] = _Validator(added="1.1")
|
| 942 |
+
"""``Provides`` (deprecated)"""
|
| 943 |
+
obsoletes: _Validator[list[str] | None] = _Validator(added="1.1")
|
| 944 |
+
"""``Obsoletes`` (deprecated)"""
|
| 945 |
+
|
| 946 |
+
def as_rfc822(self) -> RFC822Message:
|
| 947 |
+
"""
|
| 948 |
+
Return an RFC822 message with the metadata.
|
| 949 |
+
"""
|
| 950 |
+
message = RFC822Message()
|
| 951 |
+
self._write_metadata(message)
|
| 952 |
+
return message
|
| 953 |
+
|
| 954 |
+
def _write_metadata(self, message: RFC822Message) -> None:
|
| 955 |
+
"""
|
| 956 |
+
Return an RFC822 message with the metadata.
|
| 957 |
+
"""
|
| 958 |
+
for name, validator in self.__class__.__dict__.items():
|
| 959 |
+
if isinstance(validator, _Validator) and name != "description":
|
| 960 |
+
value = getattr(self, name)
|
| 961 |
+
email_name = _RAW_TO_EMAIL_MAPPING[name]
|
| 962 |
+
if value is not None:
|
| 963 |
+
if email_name == "project-url":
|
| 964 |
+
for label, url in value.items():
|
| 965 |
+
message[email_name] = f"{label}, {url}"
|
| 966 |
+
elif email_name == "keywords":
|
| 967 |
+
message[email_name] = ",".join(value)
|
| 968 |
+
elif email_name == "import-name" and value == []:
|
| 969 |
+
message[email_name] = ""
|
| 970 |
+
elif isinstance(value, list):
|
| 971 |
+
for item in value:
|
| 972 |
+
message[email_name] = str(item)
|
| 973 |
+
else:
|
| 974 |
+
message[email_name] = str(value)
|
| 975 |
+
|
| 976 |
+
# The description is a special case because it is in the body of the message.
|
| 977 |
+
if self.description is not None:
|
| 978 |
+
message.set_payload(self.description)
|
.venv/lib/python3.13/site-packages/packaging/py.typed
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/packaging/pylock.py
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import dataclasses
|
| 4 |
+
import logging
|
| 5 |
+
import re
|
| 6 |
+
from collections.abc import Mapping, Sequence
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
from typing import (
|
| 10 |
+
TYPE_CHECKING,
|
| 11 |
+
Any,
|
| 12 |
+
Callable,
|
| 13 |
+
Protocol,
|
| 14 |
+
TypeVar,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
from .markers import Marker
|
| 18 |
+
from .specifiers import SpecifierSet
|
| 19 |
+
from .utils import NormalizedName, is_normalized_name
|
| 20 |
+
from .version import Version
|
| 21 |
+
|
| 22 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
|
| 25 |
+
from typing_extensions import Self
|
| 26 |
+
|
| 27 |
+
_logger = logging.getLogger(__name__)
|
| 28 |
+
|
| 29 |
+
__all__ = [
|
| 30 |
+
"Package",
|
| 31 |
+
"PackageArchive",
|
| 32 |
+
"PackageDirectory",
|
| 33 |
+
"PackageSdist",
|
| 34 |
+
"PackageVcs",
|
| 35 |
+
"PackageWheel",
|
| 36 |
+
"Pylock",
|
| 37 |
+
"PylockUnsupportedVersionError",
|
| 38 |
+
"PylockValidationError",
|
| 39 |
+
"is_valid_pylock_path",
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
_T = TypeVar("_T")
|
| 43 |
+
_T2 = TypeVar("_T2")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class _FromMappingProtocol(Protocol): # pragma: no cover
|
| 47 |
+
@classmethod
|
| 48 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self: ...
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
_FromMappingProtocolT = TypeVar("_FromMappingProtocolT", bound=_FromMappingProtocol)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
_PYLOCK_FILE_NAME_RE = re.compile(r"^pylock\.([^.]+)\.toml$")
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def is_valid_pylock_path(path: Path) -> bool:
|
| 58 |
+
"""Check if the given path is a valid pylock file path."""
|
| 59 |
+
return path.name == "pylock.toml" or bool(_PYLOCK_FILE_NAME_RE.match(path.name))
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _toml_key(key: str) -> str:
|
| 63 |
+
return key.replace("_", "-")
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def _toml_value(key: str, value: Any) -> Any: # noqa: ANN401
|
| 67 |
+
if isinstance(value, (Version, Marker, SpecifierSet)):
|
| 68 |
+
return str(value)
|
| 69 |
+
if isinstance(value, Sequence) and key == "environments":
|
| 70 |
+
return [str(v) for v in value]
|
| 71 |
+
return value
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _toml_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:
|
| 75 |
+
return {
|
| 76 |
+
_toml_key(key): _toml_value(key, value)
|
| 77 |
+
for key, value in data
|
| 78 |
+
if value is not None
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def _get(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T | None:
|
| 83 |
+
"""Get a value from the dictionary and verify it's the expected type."""
|
| 84 |
+
if (value := d.get(key)) is None:
|
| 85 |
+
return None
|
| 86 |
+
if not isinstance(value, expected_type):
|
| 87 |
+
raise PylockValidationError(
|
| 88 |
+
f"Unexpected type {type(value).__name__} "
|
| 89 |
+
f"(expected {expected_type.__name__})",
|
| 90 |
+
context=key,
|
| 91 |
+
)
|
| 92 |
+
return value
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def _get_required(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T:
|
| 96 |
+
"""Get a required value from the dictionary and verify it's the expected type."""
|
| 97 |
+
if (value := _get(d, expected_type, key)) is None:
|
| 98 |
+
raise _PylockRequiredKeyError(key)
|
| 99 |
+
return value
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def _get_sequence(
|
| 103 |
+
d: Mapping[str, Any], expected_item_type: type[_T], key: str
|
| 104 |
+
) -> Sequence[_T] | None:
|
| 105 |
+
"""Get a list value from the dictionary and verify it's the expected items type."""
|
| 106 |
+
if (value := _get(d, Sequence, key)) is None: # type: ignore[type-abstract]
|
| 107 |
+
return None
|
| 108 |
+
if isinstance(value, (str, bytes)):
|
| 109 |
+
# special case: str and bytes are Sequences, but we want to reject it
|
| 110 |
+
raise PylockValidationError(
|
| 111 |
+
f"Unexpected type {type(value).__name__} (expected Sequence)",
|
| 112 |
+
context=key,
|
| 113 |
+
)
|
| 114 |
+
for i, item in enumerate(value):
|
| 115 |
+
if not isinstance(item, expected_item_type):
|
| 116 |
+
raise PylockValidationError(
|
| 117 |
+
f"Unexpected type {type(item).__name__} "
|
| 118 |
+
f"(expected {expected_item_type.__name__})",
|
| 119 |
+
context=f"{key}[{i}]",
|
| 120 |
+
)
|
| 121 |
+
return value
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def _get_as(
|
| 125 |
+
d: Mapping[str, Any],
|
| 126 |
+
expected_type: type[_T],
|
| 127 |
+
target_type: Callable[[_T], _T2],
|
| 128 |
+
key: str,
|
| 129 |
+
) -> _T2 | None:
|
| 130 |
+
"""Get a value from the dictionary, verify it's the expected type,
|
| 131 |
+
and convert to the target type.
|
| 132 |
+
|
| 133 |
+
This assumes the target_type constructor accepts the value.
|
| 134 |
+
"""
|
| 135 |
+
if (value := _get(d, expected_type, key)) is None:
|
| 136 |
+
return None
|
| 137 |
+
try:
|
| 138 |
+
return target_type(value)
|
| 139 |
+
except Exception as e:
|
| 140 |
+
raise PylockValidationError(e, context=key) from e
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def _get_required_as(
|
| 144 |
+
d: Mapping[str, Any],
|
| 145 |
+
expected_type: type[_T],
|
| 146 |
+
target_type: Callable[[_T], _T2],
|
| 147 |
+
key: str,
|
| 148 |
+
) -> _T2:
|
| 149 |
+
"""Get a required value from the dict, verify it's the expected type,
|
| 150 |
+
and convert to the target type."""
|
| 151 |
+
if (value := _get_as(d, expected_type, target_type, key)) is None:
|
| 152 |
+
raise _PylockRequiredKeyError(key)
|
| 153 |
+
return value
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def _get_sequence_as(
|
| 157 |
+
d: Mapping[str, Any],
|
| 158 |
+
expected_item_type: type[_T],
|
| 159 |
+
target_item_type: Callable[[_T], _T2],
|
| 160 |
+
key: str,
|
| 161 |
+
) -> list[_T2] | None:
|
| 162 |
+
"""Get list value from dictionary and verify expected items type."""
|
| 163 |
+
if (value := _get_sequence(d, expected_item_type, key)) is None:
|
| 164 |
+
return None
|
| 165 |
+
result = []
|
| 166 |
+
try:
|
| 167 |
+
for item in value:
|
| 168 |
+
typed_item = target_item_type(item)
|
| 169 |
+
result.append(typed_item)
|
| 170 |
+
except Exception as e:
|
| 171 |
+
raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e
|
| 172 |
+
return result
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def _get_object(
|
| 176 |
+
d: Mapping[str, Any], target_type: type[_FromMappingProtocolT], key: str
|
| 177 |
+
) -> _FromMappingProtocolT | None:
|
| 178 |
+
"""Get a dictionary value from the dictionary and convert it to a dataclass."""
|
| 179 |
+
if (value := _get(d, Mapping, key)) is None: # type: ignore[type-abstract]
|
| 180 |
+
return None
|
| 181 |
+
try:
|
| 182 |
+
return target_type._from_dict(value)
|
| 183 |
+
except Exception as e:
|
| 184 |
+
raise PylockValidationError(e, context=key) from e
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def _get_sequence_of_objects(
|
| 188 |
+
d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str
|
| 189 |
+
) -> list[_FromMappingProtocolT] | None:
|
| 190 |
+
"""Get a list value from the dictionary and convert its items to a dataclass."""
|
| 191 |
+
if (value := _get_sequence(d, Mapping, key)) is None: # type: ignore[type-abstract]
|
| 192 |
+
return None
|
| 193 |
+
result: list[_FromMappingProtocolT] = []
|
| 194 |
+
try:
|
| 195 |
+
for item in value:
|
| 196 |
+
typed_item = target_item_type._from_dict(item)
|
| 197 |
+
result.append(typed_item)
|
| 198 |
+
except Exception as e:
|
| 199 |
+
raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e
|
| 200 |
+
return result
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def _get_required_sequence_of_objects(
|
| 204 |
+
d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str
|
| 205 |
+
) -> Sequence[_FromMappingProtocolT]:
|
| 206 |
+
"""Get a required list value from the dictionary and convert its items to a
|
| 207 |
+
dataclass."""
|
| 208 |
+
if (result := _get_sequence_of_objects(d, target_item_type, key)) is None:
|
| 209 |
+
raise _PylockRequiredKeyError(key)
|
| 210 |
+
return result
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def _validate_normalized_name(name: str) -> NormalizedName:
|
| 214 |
+
"""Validate that a string is a NormalizedName."""
|
| 215 |
+
if not is_normalized_name(name):
|
| 216 |
+
raise PylockValidationError(f"Name {name!r} is not normalized")
|
| 217 |
+
return NormalizedName(name)
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def _validate_path_url(path: str | None, url: str | None) -> None:
|
| 221 |
+
if not path and not url:
|
| 222 |
+
raise PylockValidationError("path or url must be provided")
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def _validate_hashes(hashes: Mapping[str, Any]) -> Mapping[str, Any]:
|
| 226 |
+
if not hashes:
|
| 227 |
+
raise PylockValidationError("At least one hash must be provided")
|
| 228 |
+
if not all(isinstance(hash_val, str) for hash_val in hashes.values()):
|
| 229 |
+
raise PylockValidationError("Hash values must be strings")
|
| 230 |
+
return hashes
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
class PylockValidationError(Exception):
|
| 234 |
+
"""Raised when when input data is not spec-compliant."""
|
| 235 |
+
|
| 236 |
+
context: str | None = None
|
| 237 |
+
message: str
|
| 238 |
+
|
| 239 |
+
def __init__(
|
| 240 |
+
self,
|
| 241 |
+
cause: str | Exception,
|
| 242 |
+
*,
|
| 243 |
+
context: str | None = None,
|
| 244 |
+
) -> None:
|
| 245 |
+
if isinstance(cause, PylockValidationError):
|
| 246 |
+
if cause.context:
|
| 247 |
+
self.context = (
|
| 248 |
+
f"{context}.{cause.context}" if context else cause.context
|
| 249 |
+
)
|
| 250 |
+
else:
|
| 251 |
+
self.context = context
|
| 252 |
+
self.message = cause.message
|
| 253 |
+
else:
|
| 254 |
+
self.context = context
|
| 255 |
+
self.message = str(cause)
|
| 256 |
+
|
| 257 |
+
def __str__(self) -> str:
|
| 258 |
+
if self.context:
|
| 259 |
+
return f"{self.message} in {self.context!r}"
|
| 260 |
+
return self.message
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
class _PylockRequiredKeyError(PylockValidationError):
|
| 264 |
+
def __init__(self, key: str) -> None:
|
| 265 |
+
super().__init__("Missing required value", context=key)
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
class PylockUnsupportedVersionError(PylockValidationError):
|
| 269 |
+
"""Raised when encountering an unsupported `lock_version`."""
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
@dataclass(frozen=True, init=False)
|
| 273 |
+
class PackageVcs:
|
| 274 |
+
type: str
|
| 275 |
+
url: str | None = None
|
| 276 |
+
path: str | None = None
|
| 277 |
+
requested_revision: str | None = None
|
| 278 |
+
commit_id: str # type: ignore[misc]
|
| 279 |
+
subdirectory: str | None = None
|
| 280 |
+
|
| 281 |
+
def __init__(
|
| 282 |
+
self,
|
| 283 |
+
*,
|
| 284 |
+
type: str,
|
| 285 |
+
url: str | None = None,
|
| 286 |
+
path: str | None = None,
|
| 287 |
+
requested_revision: str | None = None,
|
| 288 |
+
commit_id: str,
|
| 289 |
+
subdirectory: str | None = None,
|
| 290 |
+
) -> None:
|
| 291 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 292 |
+
object.__setattr__(self, "type", type)
|
| 293 |
+
object.__setattr__(self, "url", url)
|
| 294 |
+
object.__setattr__(self, "path", path)
|
| 295 |
+
object.__setattr__(self, "requested_revision", requested_revision)
|
| 296 |
+
object.__setattr__(self, "commit_id", commit_id)
|
| 297 |
+
object.__setattr__(self, "subdirectory", subdirectory)
|
| 298 |
+
|
| 299 |
+
@classmethod
|
| 300 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 301 |
+
package_vcs = cls(
|
| 302 |
+
type=_get_required(d, str, "type"),
|
| 303 |
+
url=_get(d, str, "url"),
|
| 304 |
+
path=_get(d, str, "path"),
|
| 305 |
+
requested_revision=_get(d, str, "requested-revision"),
|
| 306 |
+
commit_id=_get_required(d, str, "commit-id"),
|
| 307 |
+
subdirectory=_get(d, str, "subdirectory"),
|
| 308 |
+
)
|
| 309 |
+
_validate_path_url(package_vcs.path, package_vcs.url)
|
| 310 |
+
return package_vcs
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
@dataclass(frozen=True, init=False)
|
| 314 |
+
class PackageDirectory:
|
| 315 |
+
path: str
|
| 316 |
+
editable: bool | None = None
|
| 317 |
+
subdirectory: str | None = None
|
| 318 |
+
|
| 319 |
+
def __init__(
|
| 320 |
+
self,
|
| 321 |
+
*,
|
| 322 |
+
path: str,
|
| 323 |
+
editable: bool | None = None,
|
| 324 |
+
subdirectory: str | None = None,
|
| 325 |
+
) -> None:
|
| 326 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 327 |
+
object.__setattr__(self, "path", path)
|
| 328 |
+
object.__setattr__(self, "editable", editable)
|
| 329 |
+
object.__setattr__(self, "subdirectory", subdirectory)
|
| 330 |
+
|
| 331 |
+
@classmethod
|
| 332 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 333 |
+
return cls(
|
| 334 |
+
path=_get_required(d, str, "path"),
|
| 335 |
+
editable=_get(d, bool, "editable"),
|
| 336 |
+
subdirectory=_get(d, str, "subdirectory"),
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
@dataclass(frozen=True, init=False)
|
| 341 |
+
class PackageArchive:
|
| 342 |
+
url: str | None = None
|
| 343 |
+
path: str | None = None
|
| 344 |
+
size: int | None = None
|
| 345 |
+
upload_time: datetime | None = None
|
| 346 |
+
hashes: Mapping[str, str] # type: ignore[misc]
|
| 347 |
+
subdirectory: str | None = None
|
| 348 |
+
|
| 349 |
+
def __init__(
|
| 350 |
+
self,
|
| 351 |
+
*,
|
| 352 |
+
url: str | None = None,
|
| 353 |
+
path: str | None = None,
|
| 354 |
+
size: int | None = None,
|
| 355 |
+
upload_time: datetime | None = None,
|
| 356 |
+
hashes: Mapping[str, str],
|
| 357 |
+
subdirectory: str | None = None,
|
| 358 |
+
) -> None:
|
| 359 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 360 |
+
object.__setattr__(self, "url", url)
|
| 361 |
+
object.__setattr__(self, "path", path)
|
| 362 |
+
object.__setattr__(self, "size", size)
|
| 363 |
+
object.__setattr__(self, "upload_time", upload_time)
|
| 364 |
+
object.__setattr__(self, "hashes", hashes)
|
| 365 |
+
object.__setattr__(self, "subdirectory", subdirectory)
|
| 366 |
+
|
| 367 |
+
@classmethod
|
| 368 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 369 |
+
package_archive = cls(
|
| 370 |
+
url=_get(d, str, "url"),
|
| 371 |
+
path=_get(d, str, "path"),
|
| 372 |
+
size=_get(d, int, "size"),
|
| 373 |
+
upload_time=_get(d, datetime, "upload-time"),
|
| 374 |
+
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
|
| 375 |
+
subdirectory=_get(d, str, "subdirectory"),
|
| 376 |
+
)
|
| 377 |
+
_validate_path_url(package_archive.path, package_archive.url)
|
| 378 |
+
return package_archive
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
@dataclass(frozen=True, init=False)
|
| 382 |
+
class PackageSdist:
|
| 383 |
+
name: str | None = None
|
| 384 |
+
upload_time: datetime | None = None
|
| 385 |
+
url: str | None = None
|
| 386 |
+
path: str | None = None
|
| 387 |
+
size: int | None = None
|
| 388 |
+
hashes: Mapping[str, str] # type: ignore[misc]
|
| 389 |
+
|
| 390 |
+
def __init__(
|
| 391 |
+
self,
|
| 392 |
+
*,
|
| 393 |
+
name: str | None = None,
|
| 394 |
+
upload_time: datetime | None = None,
|
| 395 |
+
url: str | None = None,
|
| 396 |
+
path: str | None = None,
|
| 397 |
+
size: int | None = None,
|
| 398 |
+
hashes: Mapping[str, str],
|
| 399 |
+
) -> None:
|
| 400 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 401 |
+
object.__setattr__(self, "name", name)
|
| 402 |
+
object.__setattr__(self, "upload_time", upload_time)
|
| 403 |
+
object.__setattr__(self, "url", url)
|
| 404 |
+
object.__setattr__(self, "path", path)
|
| 405 |
+
object.__setattr__(self, "size", size)
|
| 406 |
+
object.__setattr__(self, "hashes", hashes)
|
| 407 |
+
|
| 408 |
+
@classmethod
|
| 409 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 410 |
+
package_sdist = cls(
|
| 411 |
+
name=_get(d, str, "name"),
|
| 412 |
+
upload_time=_get(d, datetime, "upload-time"),
|
| 413 |
+
url=_get(d, str, "url"),
|
| 414 |
+
path=_get(d, str, "path"),
|
| 415 |
+
size=_get(d, int, "size"),
|
| 416 |
+
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
|
| 417 |
+
)
|
| 418 |
+
_validate_path_url(package_sdist.path, package_sdist.url)
|
| 419 |
+
return package_sdist
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
@dataclass(frozen=True, init=False)
|
| 423 |
+
class PackageWheel:
|
| 424 |
+
name: str | None = None
|
| 425 |
+
upload_time: datetime | None = None
|
| 426 |
+
url: str | None = None
|
| 427 |
+
path: str | None = None
|
| 428 |
+
size: int | None = None
|
| 429 |
+
hashes: Mapping[str, str] # type: ignore[misc]
|
| 430 |
+
|
| 431 |
+
def __init__(
|
| 432 |
+
self,
|
| 433 |
+
*,
|
| 434 |
+
name: str | None = None,
|
| 435 |
+
upload_time: datetime | None = None,
|
| 436 |
+
url: str | None = None,
|
| 437 |
+
path: str | None = None,
|
| 438 |
+
size: int | None = None,
|
| 439 |
+
hashes: Mapping[str, str],
|
| 440 |
+
) -> None:
|
| 441 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 442 |
+
object.__setattr__(self, "name", name)
|
| 443 |
+
object.__setattr__(self, "upload_time", upload_time)
|
| 444 |
+
object.__setattr__(self, "url", url)
|
| 445 |
+
object.__setattr__(self, "path", path)
|
| 446 |
+
object.__setattr__(self, "size", size)
|
| 447 |
+
object.__setattr__(self, "hashes", hashes)
|
| 448 |
+
|
| 449 |
+
@classmethod
|
| 450 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 451 |
+
package_wheel = cls(
|
| 452 |
+
name=_get(d, str, "name"),
|
| 453 |
+
upload_time=_get(d, datetime, "upload-time"),
|
| 454 |
+
url=_get(d, str, "url"),
|
| 455 |
+
path=_get(d, str, "path"),
|
| 456 |
+
size=_get(d, int, "size"),
|
| 457 |
+
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
|
| 458 |
+
)
|
| 459 |
+
_validate_path_url(package_wheel.path, package_wheel.url)
|
| 460 |
+
return package_wheel
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
@dataclass(frozen=True, init=False)
|
| 464 |
+
class Package:
|
| 465 |
+
name: NormalizedName
|
| 466 |
+
version: Version | None = None
|
| 467 |
+
marker: Marker | None = None
|
| 468 |
+
requires_python: SpecifierSet | None = None
|
| 469 |
+
dependencies: Sequence[Mapping[str, Any]] | None = None
|
| 470 |
+
vcs: PackageVcs | None = None
|
| 471 |
+
directory: PackageDirectory | None = None
|
| 472 |
+
archive: PackageArchive | None = None
|
| 473 |
+
index: str | None = None
|
| 474 |
+
sdist: PackageSdist | None = None
|
| 475 |
+
wheels: Sequence[PackageWheel] | None = None
|
| 476 |
+
attestation_identities: Sequence[Mapping[str, Any]] | None = None
|
| 477 |
+
tool: Mapping[str, Any] | None = None
|
| 478 |
+
|
| 479 |
+
def __init__(
|
| 480 |
+
self,
|
| 481 |
+
*,
|
| 482 |
+
name: NormalizedName,
|
| 483 |
+
version: Version | None = None,
|
| 484 |
+
marker: Marker | None = None,
|
| 485 |
+
requires_python: SpecifierSet | None = None,
|
| 486 |
+
dependencies: Sequence[Mapping[str, Any]] | None = None,
|
| 487 |
+
vcs: PackageVcs | None = None,
|
| 488 |
+
directory: PackageDirectory | None = None,
|
| 489 |
+
archive: PackageArchive | None = None,
|
| 490 |
+
index: str | None = None,
|
| 491 |
+
sdist: PackageSdist | None = None,
|
| 492 |
+
wheels: Sequence[PackageWheel] | None = None,
|
| 493 |
+
attestation_identities: Sequence[Mapping[str, Any]] | None = None,
|
| 494 |
+
tool: Mapping[str, Any] | None = None,
|
| 495 |
+
) -> None:
|
| 496 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 497 |
+
object.__setattr__(self, "name", name)
|
| 498 |
+
object.__setattr__(self, "version", version)
|
| 499 |
+
object.__setattr__(self, "marker", marker)
|
| 500 |
+
object.__setattr__(self, "requires_python", requires_python)
|
| 501 |
+
object.__setattr__(self, "dependencies", dependencies)
|
| 502 |
+
object.__setattr__(self, "vcs", vcs)
|
| 503 |
+
object.__setattr__(self, "directory", directory)
|
| 504 |
+
object.__setattr__(self, "archive", archive)
|
| 505 |
+
object.__setattr__(self, "index", index)
|
| 506 |
+
object.__setattr__(self, "sdist", sdist)
|
| 507 |
+
object.__setattr__(self, "wheels", wheels)
|
| 508 |
+
object.__setattr__(self, "attestation_identities", attestation_identities)
|
| 509 |
+
object.__setattr__(self, "tool", tool)
|
| 510 |
+
|
| 511 |
+
@classmethod
|
| 512 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 513 |
+
package = cls(
|
| 514 |
+
name=_get_required_as(d, str, _validate_normalized_name, "name"),
|
| 515 |
+
version=_get_as(d, str, Version, "version"),
|
| 516 |
+
requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
|
| 517 |
+
dependencies=_get_sequence(d, Mapping, "dependencies"), # type: ignore[type-abstract]
|
| 518 |
+
marker=_get_as(d, str, Marker, "marker"),
|
| 519 |
+
vcs=_get_object(d, PackageVcs, "vcs"),
|
| 520 |
+
directory=_get_object(d, PackageDirectory, "directory"),
|
| 521 |
+
archive=_get_object(d, PackageArchive, "archive"),
|
| 522 |
+
index=_get(d, str, "index"),
|
| 523 |
+
sdist=_get_object(d, PackageSdist, "sdist"),
|
| 524 |
+
wheels=_get_sequence_of_objects(d, PackageWheel, "wheels"),
|
| 525 |
+
attestation_identities=_get_sequence(d, Mapping, "attestation-identities"), # type: ignore[type-abstract]
|
| 526 |
+
tool=_get(d, Mapping, "tool"), # type: ignore[type-abstract]
|
| 527 |
+
)
|
| 528 |
+
distributions = bool(package.sdist) + len(package.wheels or [])
|
| 529 |
+
direct_urls = (
|
| 530 |
+
bool(package.vcs) + bool(package.directory) + bool(package.archive)
|
| 531 |
+
)
|
| 532 |
+
if distributions > 0 and direct_urls > 0:
|
| 533 |
+
raise PylockValidationError(
|
| 534 |
+
"None of vcs, directory, archive must be set if sdist or wheels are set"
|
| 535 |
+
)
|
| 536 |
+
if distributions == 0 and direct_urls != 1:
|
| 537 |
+
raise PylockValidationError(
|
| 538 |
+
"Exactly one of vcs, directory, archive must be set "
|
| 539 |
+
"if sdist and wheels are not set"
|
| 540 |
+
)
|
| 541 |
+
try:
|
| 542 |
+
for i, attestation_identity in enumerate( # noqa: B007
|
| 543 |
+
package.attestation_identities or []
|
| 544 |
+
):
|
| 545 |
+
_get_required(attestation_identity, str, "kind")
|
| 546 |
+
except Exception as e:
|
| 547 |
+
raise PylockValidationError(
|
| 548 |
+
e, context=f"attestation-identities[{i}]"
|
| 549 |
+
) from e
|
| 550 |
+
return package
|
| 551 |
+
|
| 552 |
+
@property
|
| 553 |
+
def is_direct(self) -> bool:
|
| 554 |
+
return not (self.sdist or self.wheels)
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
@dataclass(frozen=True, init=False)
|
| 558 |
+
class Pylock:
|
| 559 |
+
"""A class representing a pylock file."""
|
| 560 |
+
|
| 561 |
+
lock_version: Version
|
| 562 |
+
environments: Sequence[Marker] | None = None
|
| 563 |
+
requires_python: SpecifierSet | None = None
|
| 564 |
+
extras: Sequence[NormalizedName] | None = None
|
| 565 |
+
dependency_groups: Sequence[str] | None = None
|
| 566 |
+
default_groups: Sequence[str] | None = None
|
| 567 |
+
created_by: str # type: ignore[misc]
|
| 568 |
+
packages: Sequence[Package] # type: ignore[misc]
|
| 569 |
+
tool: Mapping[str, Any] | None = None
|
| 570 |
+
|
| 571 |
+
def __init__(
|
| 572 |
+
self,
|
| 573 |
+
*,
|
| 574 |
+
lock_version: Version,
|
| 575 |
+
environments: Sequence[Marker] | None = None,
|
| 576 |
+
requires_python: SpecifierSet | None = None,
|
| 577 |
+
extras: Sequence[NormalizedName] | None = None,
|
| 578 |
+
dependency_groups: Sequence[str] | None = None,
|
| 579 |
+
default_groups: Sequence[str] | None = None,
|
| 580 |
+
created_by: str,
|
| 581 |
+
packages: Sequence[Package],
|
| 582 |
+
tool: Mapping[str, Any] | None = None,
|
| 583 |
+
) -> None:
|
| 584 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 585 |
+
object.__setattr__(self, "lock_version", lock_version)
|
| 586 |
+
object.__setattr__(self, "environments", environments)
|
| 587 |
+
object.__setattr__(self, "requires_python", requires_python)
|
| 588 |
+
object.__setattr__(self, "extras", extras)
|
| 589 |
+
object.__setattr__(self, "dependency_groups", dependency_groups)
|
| 590 |
+
object.__setattr__(self, "default_groups", default_groups)
|
| 591 |
+
object.__setattr__(self, "created_by", created_by)
|
| 592 |
+
object.__setattr__(self, "packages", packages)
|
| 593 |
+
object.__setattr__(self, "tool", tool)
|
| 594 |
+
|
| 595 |
+
@classmethod
|
| 596 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 597 |
+
pylock = cls(
|
| 598 |
+
lock_version=_get_required_as(d, str, Version, "lock-version"),
|
| 599 |
+
environments=_get_sequence_as(d, str, Marker, "environments"),
|
| 600 |
+
extras=_get_sequence_as(d, str, _validate_normalized_name, "extras"),
|
| 601 |
+
dependency_groups=_get_sequence(d, str, "dependency-groups"),
|
| 602 |
+
default_groups=_get_sequence(d, str, "default-groups"),
|
| 603 |
+
created_by=_get_required(d, str, "created-by"),
|
| 604 |
+
requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
|
| 605 |
+
packages=_get_required_sequence_of_objects(d, Package, "packages"),
|
| 606 |
+
tool=_get(d, Mapping, "tool"), # type: ignore[type-abstract]
|
| 607 |
+
)
|
| 608 |
+
if not Version("1") <= pylock.lock_version < Version("2"):
|
| 609 |
+
raise PylockUnsupportedVersionError(
|
| 610 |
+
f"pylock version {pylock.lock_version} is not supported"
|
| 611 |
+
)
|
| 612 |
+
if pylock.lock_version > Version("1.0"):
|
| 613 |
+
_logger.warning(
|
| 614 |
+
"pylock minor version %s is not supported", pylock.lock_version
|
| 615 |
+
)
|
| 616 |
+
return pylock
|
| 617 |
+
|
| 618 |
+
@classmethod
|
| 619 |
+
def from_dict(cls, d: Mapping[str, Any], /) -> Self:
|
| 620 |
+
"""Create and validate a Pylock instance from a TOML dictionary.
|
| 621 |
+
|
| 622 |
+
Raises :class:`PylockValidationError` if the input data is not
|
| 623 |
+
spec-compliant.
|
| 624 |
+
"""
|
| 625 |
+
return cls._from_dict(d)
|
| 626 |
+
|
| 627 |
+
def to_dict(self) -> Mapping[str, Any]:
|
| 628 |
+
"""Convert the Pylock instance to a TOML dictionary."""
|
| 629 |
+
return dataclasses.asdict(self, dict_factory=_toml_dict_factory)
|
| 630 |
+
|
| 631 |
+
def validate(self) -> None:
|
| 632 |
+
"""Validate the Pylock instance against the specification.
|
| 633 |
+
|
| 634 |
+
Raises :class:`PylockValidationError` otherwise."""
|
| 635 |
+
self.from_dict(self.to_dict())
|
.venv/lib/python3.13/site-packages/packaging/requirements.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
from typing import Iterator
|
| 7 |
+
|
| 8 |
+
from ._parser import parse_requirement as _parse_requirement
|
| 9 |
+
from ._tokenizer import ParserSyntaxError
|
| 10 |
+
from .markers import Marker, _normalize_extra_values
|
| 11 |
+
from .specifiers import SpecifierSet
|
| 12 |
+
from .utils import canonicalize_name
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class InvalidRequirement(ValueError):
|
| 16 |
+
"""
|
| 17 |
+
An invalid requirement was found, users should refer to PEP 508.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class Requirement:
|
| 22 |
+
"""Parse a requirement.
|
| 23 |
+
|
| 24 |
+
Parse a given requirement string into its parts, such as name, specifier,
|
| 25 |
+
URL, and extras. Raises InvalidRequirement on a badly-formed requirement
|
| 26 |
+
string.
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
# TODO: Can we test whether something is contained within a requirement?
|
| 30 |
+
# If so how do we do that? Do we need to test against the _name_ of
|
| 31 |
+
# the thing as well as the version? What about the markers?
|
| 32 |
+
# TODO: Can we normalize the name and extra name?
|
| 33 |
+
|
| 34 |
+
def __init__(self, requirement_string: str) -> None:
|
| 35 |
+
try:
|
| 36 |
+
parsed = _parse_requirement(requirement_string)
|
| 37 |
+
except ParserSyntaxError as e:
|
| 38 |
+
raise InvalidRequirement(str(e)) from e
|
| 39 |
+
|
| 40 |
+
self.name: str = parsed.name
|
| 41 |
+
self.url: str | None = parsed.url or None
|
| 42 |
+
self.extras: set[str] = set(parsed.extras or [])
|
| 43 |
+
self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)
|
| 44 |
+
self.marker: Marker | None = None
|
| 45 |
+
if parsed.marker is not None:
|
| 46 |
+
self.marker = Marker.__new__(Marker)
|
| 47 |
+
self.marker._markers = _normalize_extra_values(parsed.marker)
|
| 48 |
+
|
| 49 |
+
def _iter_parts(self, name: str) -> Iterator[str]:
|
| 50 |
+
yield name
|
| 51 |
+
|
| 52 |
+
if self.extras:
|
| 53 |
+
formatted_extras = ",".join(sorted(self.extras))
|
| 54 |
+
yield f"[{formatted_extras}]"
|
| 55 |
+
|
| 56 |
+
if self.specifier:
|
| 57 |
+
yield str(self.specifier)
|
| 58 |
+
|
| 59 |
+
if self.url:
|
| 60 |
+
yield f" @ {self.url}"
|
| 61 |
+
if self.marker:
|
| 62 |
+
yield " "
|
| 63 |
+
|
| 64 |
+
if self.marker:
|
| 65 |
+
yield f"; {self.marker}"
|
| 66 |
+
|
| 67 |
+
def __str__(self) -> str:
|
| 68 |
+
return "".join(self._iter_parts(self.name))
|
| 69 |
+
|
| 70 |
+
def __repr__(self) -> str:
|
| 71 |
+
return f"<{self.__class__.__name__}('{self}')>"
|
| 72 |
+
|
| 73 |
+
def __hash__(self) -> int:
|
| 74 |
+
return hash(tuple(self._iter_parts(canonicalize_name(self.name))))
|
| 75 |
+
|
| 76 |
+
def __eq__(self, other: object) -> bool:
|
| 77 |
+
if not isinstance(other, Requirement):
|
| 78 |
+
return NotImplemented
|
| 79 |
+
|
| 80 |
+
return (
|
| 81 |
+
canonicalize_name(self.name) == canonicalize_name(other.name)
|
| 82 |
+
and self.extras == other.extras
|
| 83 |
+
and self.specifier == other.specifier
|
| 84 |
+
and self.url == other.url
|
| 85 |
+
and self.marker == other.marker
|
| 86 |
+
)
|
.venv/lib/python3.13/site-packages/packaging/tags.py
ADDED
|
@@ -0,0 +1,651 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import logging
|
| 8 |
+
import platform
|
| 9 |
+
import re
|
| 10 |
+
import struct
|
| 11 |
+
import subprocess
|
| 12 |
+
import sys
|
| 13 |
+
import sysconfig
|
| 14 |
+
from importlib.machinery import EXTENSION_SUFFIXES
|
| 15 |
+
from typing import (
|
| 16 |
+
Any,
|
| 17 |
+
Iterable,
|
| 18 |
+
Iterator,
|
| 19 |
+
Sequence,
|
| 20 |
+
Tuple,
|
| 21 |
+
cast,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
from . import _manylinux, _musllinux
|
| 25 |
+
|
| 26 |
+
logger = logging.getLogger(__name__)
|
| 27 |
+
|
| 28 |
+
PythonVersion = Sequence[int]
|
| 29 |
+
AppleVersion = Tuple[int, int]
|
| 30 |
+
|
| 31 |
+
INTERPRETER_SHORT_NAMES: dict[str, str] = {
|
| 32 |
+
"python": "py", # Generic.
|
| 33 |
+
"cpython": "cp",
|
| 34 |
+
"pypy": "pp",
|
| 35 |
+
"ironpython": "ip",
|
| 36 |
+
"jython": "jy",
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
_32_BIT_INTERPRETER = struct.calcsize("P") == 4
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class Tag:
|
| 44 |
+
"""
|
| 45 |
+
A representation of the tag triple for a wheel.
|
| 46 |
+
|
| 47 |
+
Instances are considered immutable and thus are hashable. Equality checking
|
| 48 |
+
is also supported.
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
__slots__ = ["_abi", "_hash", "_interpreter", "_platform"]
|
| 52 |
+
|
| 53 |
+
def __init__(self, interpreter: str, abi: str, platform: str) -> None:
|
| 54 |
+
self._interpreter = interpreter.lower()
|
| 55 |
+
self._abi = abi.lower()
|
| 56 |
+
self._platform = platform.lower()
|
| 57 |
+
# The __hash__ of every single element in a Set[Tag] will be evaluated each time
|
| 58 |
+
# that a set calls its `.disjoint()` method, which may be called hundreds of
|
| 59 |
+
# times when scanning a page of links for packages with tags matching that
|
| 60 |
+
# Set[Tag]. Pre-computing the value here produces significant speedups for
|
| 61 |
+
# downstream consumers.
|
| 62 |
+
self._hash = hash((self._interpreter, self._abi, self._platform))
|
| 63 |
+
|
| 64 |
+
@property
|
| 65 |
+
def interpreter(self) -> str:
|
| 66 |
+
return self._interpreter
|
| 67 |
+
|
| 68 |
+
@property
|
| 69 |
+
def abi(self) -> str:
|
| 70 |
+
return self._abi
|
| 71 |
+
|
| 72 |
+
@property
|
| 73 |
+
def platform(self) -> str:
|
| 74 |
+
return self._platform
|
| 75 |
+
|
| 76 |
+
def __eq__(self, other: object) -> bool:
|
| 77 |
+
if not isinstance(other, Tag):
|
| 78 |
+
return NotImplemented
|
| 79 |
+
|
| 80 |
+
return (
|
| 81 |
+
(self._hash == other._hash) # Short-circuit ASAP for perf reasons.
|
| 82 |
+
and (self._platform == other._platform)
|
| 83 |
+
and (self._abi == other._abi)
|
| 84 |
+
and (self._interpreter == other._interpreter)
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
def __hash__(self) -> int:
|
| 88 |
+
return self._hash
|
| 89 |
+
|
| 90 |
+
def __str__(self) -> str:
|
| 91 |
+
return f"{self._interpreter}-{self._abi}-{self._platform}"
|
| 92 |
+
|
| 93 |
+
def __repr__(self) -> str:
|
| 94 |
+
return f"<{self} @ {id(self)}>"
|
| 95 |
+
|
| 96 |
+
def __setstate__(self, state: tuple[None, dict[str, Any]]) -> None:
|
| 97 |
+
# The cached _hash is wrong when unpickling.
|
| 98 |
+
_, slots = state
|
| 99 |
+
for k, v in slots.items():
|
| 100 |
+
setattr(self, k, v)
|
| 101 |
+
self._hash = hash((self._interpreter, self._abi, self._platform))
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def parse_tag(tag: str) -> frozenset[Tag]:
|
| 105 |
+
"""
|
| 106 |
+
Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances.
|
| 107 |
+
|
| 108 |
+
Returning a set is required due to the possibility that the tag is a
|
| 109 |
+
compressed tag set.
|
| 110 |
+
"""
|
| 111 |
+
tags = set()
|
| 112 |
+
interpreters, abis, platforms = tag.split("-")
|
| 113 |
+
for interpreter in interpreters.split("."):
|
| 114 |
+
for abi in abis.split("."):
|
| 115 |
+
for platform_ in platforms.split("."):
|
| 116 |
+
tags.add(Tag(interpreter, abi, platform_))
|
| 117 |
+
return frozenset(tags)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def _get_config_var(name: str, warn: bool = False) -> int | str | None:
|
| 121 |
+
value: int | str | None = sysconfig.get_config_var(name)
|
| 122 |
+
if value is None and warn:
|
| 123 |
+
logger.debug(
|
| 124 |
+
"Config variable '%s' is unset, Python ABI tag may be incorrect", name
|
| 125 |
+
)
|
| 126 |
+
return value
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def _normalize_string(string: str) -> str:
|
| 130 |
+
return string.replace(".", "_").replace("-", "_").replace(" ", "_")
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def _is_threaded_cpython(abis: list[str]) -> bool:
|
| 134 |
+
"""
|
| 135 |
+
Determine if the ABI corresponds to a threaded (`--disable-gil`) build.
|
| 136 |
+
|
| 137 |
+
The threaded builds are indicated by a "t" in the abiflags.
|
| 138 |
+
"""
|
| 139 |
+
if len(abis) == 0:
|
| 140 |
+
return False
|
| 141 |
+
# expect e.g., cp313
|
| 142 |
+
m = re.match(r"cp\d+(.*)", abis[0])
|
| 143 |
+
if not m:
|
| 144 |
+
return False
|
| 145 |
+
abiflags = m.group(1)
|
| 146 |
+
return "t" in abiflags
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool:
|
| 150 |
+
"""
|
| 151 |
+
Determine if the Python version supports abi3.
|
| 152 |
+
|
| 153 |
+
PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`)
|
| 154 |
+
builds do not support abi3.
|
| 155 |
+
"""
|
| 156 |
+
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]:
|
| 160 |
+
py_version = tuple(py_version) # To allow for version comparison.
|
| 161 |
+
abis = []
|
| 162 |
+
version = _version_nodot(py_version[:2])
|
| 163 |
+
threading = debug = pymalloc = ucs4 = ""
|
| 164 |
+
with_debug = _get_config_var("Py_DEBUG", warn)
|
| 165 |
+
has_refcount = hasattr(sys, "gettotalrefcount")
|
| 166 |
+
# Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
|
| 167 |
+
# extension modules is the best option.
|
| 168 |
+
# https://github.com/pypa/pip/issues/3383#issuecomment-173267692
|
| 169 |
+
has_ext = "_d.pyd" in EXTENSION_SUFFIXES
|
| 170 |
+
if with_debug or (with_debug is None and (has_refcount or has_ext)):
|
| 171 |
+
debug = "d"
|
| 172 |
+
if py_version >= (3, 13) and _get_config_var("Py_GIL_DISABLED", warn):
|
| 173 |
+
threading = "t"
|
| 174 |
+
if py_version < (3, 8):
|
| 175 |
+
with_pymalloc = _get_config_var("WITH_PYMALLOC", warn)
|
| 176 |
+
if with_pymalloc or with_pymalloc is None:
|
| 177 |
+
pymalloc = "m"
|
| 178 |
+
if py_version < (3, 3):
|
| 179 |
+
unicode_size = _get_config_var("Py_UNICODE_SIZE", warn)
|
| 180 |
+
if unicode_size == 4 or (
|
| 181 |
+
unicode_size is None and sys.maxunicode == 0x10FFFF
|
| 182 |
+
):
|
| 183 |
+
ucs4 = "u"
|
| 184 |
+
elif debug:
|
| 185 |
+
# Debug builds can also load "normal" extension modules.
|
| 186 |
+
# We can also assume no UCS-4 or pymalloc requirement.
|
| 187 |
+
abis.append(f"cp{version}{threading}")
|
| 188 |
+
abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}")
|
| 189 |
+
return abis
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def cpython_tags(
|
| 193 |
+
python_version: PythonVersion | None = None,
|
| 194 |
+
abis: Iterable[str] | None = None,
|
| 195 |
+
platforms: Iterable[str] | None = None,
|
| 196 |
+
*,
|
| 197 |
+
warn: bool = False,
|
| 198 |
+
) -> Iterator[Tag]:
|
| 199 |
+
"""
|
| 200 |
+
Yields the tags for a CPython interpreter.
|
| 201 |
+
|
| 202 |
+
The tags consist of:
|
| 203 |
+
- cp<python_version>-<abi>-<platform>
|
| 204 |
+
- cp<python_version>-abi3-<platform>
|
| 205 |
+
- cp<python_version>-none-<platform>
|
| 206 |
+
- cp<less than python_version>-abi3-<platform> # Older Python versions down to 3.2.
|
| 207 |
+
|
| 208 |
+
If python_version only specifies a major version then user-provided ABIs and
|
| 209 |
+
the 'none' ABItag will be used.
|
| 210 |
+
|
| 211 |
+
If 'abi3' or 'none' are specified in 'abis' then they will be yielded at
|
| 212 |
+
their normal position and not at the beginning.
|
| 213 |
+
"""
|
| 214 |
+
if not python_version:
|
| 215 |
+
python_version = sys.version_info[:2]
|
| 216 |
+
|
| 217 |
+
interpreter = f"cp{_version_nodot(python_version[:2])}"
|
| 218 |
+
|
| 219 |
+
if abis is None:
|
| 220 |
+
abis = _cpython_abis(python_version, warn) if len(python_version) > 1 else []
|
| 221 |
+
abis = list(abis)
|
| 222 |
+
# 'abi3' and 'none' are explicitly handled later.
|
| 223 |
+
for explicit_abi in ("abi3", "none"):
|
| 224 |
+
try:
|
| 225 |
+
abis.remove(explicit_abi)
|
| 226 |
+
except ValueError: # noqa: PERF203
|
| 227 |
+
pass
|
| 228 |
+
|
| 229 |
+
platforms = list(platforms or platform_tags())
|
| 230 |
+
for abi in abis:
|
| 231 |
+
for platform_ in platforms:
|
| 232 |
+
yield Tag(interpreter, abi, platform_)
|
| 233 |
+
|
| 234 |
+
threading = _is_threaded_cpython(abis)
|
| 235 |
+
use_abi3 = _abi3_applies(python_version, threading)
|
| 236 |
+
if use_abi3:
|
| 237 |
+
yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
|
| 238 |
+
yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
|
| 239 |
+
|
| 240 |
+
if use_abi3:
|
| 241 |
+
for minor_version in range(python_version[1] - 1, 1, -1):
|
| 242 |
+
for platform_ in platforms:
|
| 243 |
+
version = _version_nodot((python_version[0], minor_version))
|
| 244 |
+
interpreter = f"cp{version}"
|
| 245 |
+
yield Tag(interpreter, "abi3", platform_)
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def _generic_abi() -> list[str]:
|
| 249 |
+
"""
|
| 250 |
+
Return the ABI tag based on EXT_SUFFIX.
|
| 251 |
+
"""
|
| 252 |
+
# The following are examples of `EXT_SUFFIX`.
|
| 253 |
+
# We want to keep the parts which are related to the ABI and remove the
|
| 254 |
+
# parts which are related to the platform:
|
| 255 |
+
# - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310
|
| 256 |
+
# - mac: '.cpython-310-darwin.so' => cp310
|
| 257 |
+
# - win: '.cp310-win_amd64.pyd' => cp310
|
| 258 |
+
# - win: '.pyd' => cp37 (uses _cpython_abis())
|
| 259 |
+
# - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73
|
| 260 |
+
# - graalpy: '.graalpy-38-native-x86_64-darwin.dylib'
|
| 261 |
+
# => graalpy_38_native
|
| 262 |
+
|
| 263 |
+
ext_suffix = _get_config_var("EXT_SUFFIX", warn=True)
|
| 264 |
+
if not isinstance(ext_suffix, str) or ext_suffix[0] != ".":
|
| 265 |
+
raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')")
|
| 266 |
+
parts = ext_suffix.split(".")
|
| 267 |
+
if len(parts) < 3:
|
| 268 |
+
# CPython3.7 and earlier uses ".pyd" on Windows.
|
| 269 |
+
return _cpython_abis(sys.version_info[:2])
|
| 270 |
+
soabi = parts[1]
|
| 271 |
+
if soabi.startswith("cpython"):
|
| 272 |
+
# non-windows
|
| 273 |
+
abi = "cp" + soabi.split("-")[1]
|
| 274 |
+
elif soabi.startswith("cp"):
|
| 275 |
+
# windows
|
| 276 |
+
abi = soabi.split("-")[0]
|
| 277 |
+
elif soabi.startswith("pypy"):
|
| 278 |
+
abi = "-".join(soabi.split("-")[:2])
|
| 279 |
+
elif soabi.startswith("graalpy"):
|
| 280 |
+
abi = "-".join(soabi.split("-")[:3])
|
| 281 |
+
elif soabi:
|
| 282 |
+
# pyston, ironpython, others?
|
| 283 |
+
abi = soabi
|
| 284 |
+
else:
|
| 285 |
+
return []
|
| 286 |
+
return [_normalize_string(abi)]
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def generic_tags(
|
| 290 |
+
interpreter: str | None = None,
|
| 291 |
+
abis: Iterable[str] | None = None,
|
| 292 |
+
platforms: Iterable[str] | None = None,
|
| 293 |
+
*,
|
| 294 |
+
warn: bool = False,
|
| 295 |
+
) -> Iterator[Tag]:
|
| 296 |
+
"""
|
| 297 |
+
Yields the tags for a generic interpreter.
|
| 298 |
+
|
| 299 |
+
The tags consist of:
|
| 300 |
+
- <interpreter>-<abi>-<platform>
|
| 301 |
+
|
| 302 |
+
The "none" ABI will be added if it was not explicitly provided.
|
| 303 |
+
"""
|
| 304 |
+
if not interpreter:
|
| 305 |
+
interp_name = interpreter_name()
|
| 306 |
+
interp_version = interpreter_version(warn=warn)
|
| 307 |
+
interpreter = f"{interp_name}{interp_version}"
|
| 308 |
+
abis = _generic_abi() if abis is None else list(abis)
|
| 309 |
+
platforms = list(platforms or platform_tags())
|
| 310 |
+
if "none" not in abis:
|
| 311 |
+
abis.append("none")
|
| 312 |
+
for abi in abis:
|
| 313 |
+
for platform_ in platforms:
|
| 314 |
+
yield Tag(interpreter, abi, platform_)
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
|
| 318 |
+
"""
|
| 319 |
+
Yields Python versions in descending order.
|
| 320 |
+
|
| 321 |
+
After the latest version, the major-only version will be yielded, and then
|
| 322 |
+
all previous versions of that major version.
|
| 323 |
+
"""
|
| 324 |
+
if len(py_version) > 1:
|
| 325 |
+
yield f"py{_version_nodot(py_version[:2])}"
|
| 326 |
+
yield f"py{py_version[0]}"
|
| 327 |
+
if len(py_version) > 1:
|
| 328 |
+
for minor in range(py_version[1] - 1, -1, -1):
|
| 329 |
+
yield f"py{_version_nodot((py_version[0], minor))}"
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def compatible_tags(
|
| 333 |
+
python_version: PythonVersion | None = None,
|
| 334 |
+
interpreter: str | None = None,
|
| 335 |
+
platforms: Iterable[str] | None = None,
|
| 336 |
+
) -> Iterator[Tag]:
|
| 337 |
+
"""
|
| 338 |
+
Yields the sequence of tags that are compatible with a specific version of Python.
|
| 339 |
+
|
| 340 |
+
The tags consist of:
|
| 341 |
+
- py*-none-<platform>
|
| 342 |
+
- <interpreter>-none-any # ... if `interpreter` is provided.
|
| 343 |
+
- py*-none-any
|
| 344 |
+
"""
|
| 345 |
+
if not python_version:
|
| 346 |
+
python_version = sys.version_info[:2]
|
| 347 |
+
platforms = list(platforms or platform_tags())
|
| 348 |
+
for version in _py_interpreter_range(python_version):
|
| 349 |
+
for platform_ in platforms:
|
| 350 |
+
yield Tag(version, "none", platform_)
|
| 351 |
+
if interpreter:
|
| 352 |
+
yield Tag(interpreter, "none", "any")
|
| 353 |
+
for version in _py_interpreter_range(python_version):
|
| 354 |
+
yield Tag(version, "none", "any")
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
|
| 358 |
+
if not is_32bit:
|
| 359 |
+
return arch
|
| 360 |
+
|
| 361 |
+
if arch.startswith("ppc"):
|
| 362 |
+
return "ppc"
|
| 363 |
+
|
| 364 |
+
return "i386"
|
| 365 |
+
|
| 366 |
+
|
| 367 |
+
def _mac_binary_formats(version: AppleVersion, cpu_arch: str) -> list[str]:
|
| 368 |
+
formats = [cpu_arch]
|
| 369 |
+
if cpu_arch == "x86_64":
|
| 370 |
+
if version < (10, 4):
|
| 371 |
+
return []
|
| 372 |
+
formats.extend(["intel", "fat64", "fat32"])
|
| 373 |
+
|
| 374 |
+
elif cpu_arch == "i386":
|
| 375 |
+
if version < (10, 4):
|
| 376 |
+
return []
|
| 377 |
+
formats.extend(["intel", "fat32", "fat"])
|
| 378 |
+
|
| 379 |
+
elif cpu_arch == "ppc64":
|
| 380 |
+
# TODO: Need to care about 32-bit PPC for ppc64 through 10.2?
|
| 381 |
+
if version > (10, 5) or version < (10, 4):
|
| 382 |
+
return []
|
| 383 |
+
formats.append("fat64")
|
| 384 |
+
|
| 385 |
+
elif cpu_arch == "ppc":
|
| 386 |
+
if version > (10, 6):
|
| 387 |
+
return []
|
| 388 |
+
formats.extend(["fat32", "fat"])
|
| 389 |
+
|
| 390 |
+
if cpu_arch in {"arm64", "x86_64"}:
|
| 391 |
+
formats.append("universal2")
|
| 392 |
+
|
| 393 |
+
if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}:
|
| 394 |
+
formats.append("universal")
|
| 395 |
+
|
| 396 |
+
return formats
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def mac_platforms(
|
| 400 |
+
version: AppleVersion | None = None, arch: str | None = None
|
| 401 |
+
) -> Iterator[str]:
|
| 402 |
+
"""
|
| 403 |
+
Yields the platform tags for a macOS system.
|
| 404 |
+
|
| 405 |
+
The `version` parameter is a two-item tuple specifying the macOS version to
|
| 406 |
+
generate platform tags for. The `arch` parameter is the CPU architecture to
|
| 407 |
+
generate platform tags for. Both parameters default to the appropriate value
|
| 408 |
+
for the current system.
|
| 409 |
+
"""
|
| 410 |
+
version_str, _, cpu_arch = platform.mac_ver()
|
| 411 |
+
if version is None:
|
| 412 |
+
version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
|
| 413 |
+
if version == (10, 16):
|
| 414 |
+
# When built against an older macOS SDK, Python will report macOS 10.16
|
| 415 |
+
# instead of the real version.
|
| 416 |
+
version_str = subprocess.run(
|
| 417 |
+
[
|
| 418 |
+
sys.executable,
|
| 419 |
+
"-sS",
|
| 420 |
+
"-c",
|
| 421 |
+
"import platform; print(platform.mac_ver()[0])",
|
| 422 |
+
],
|
| 423 |
+
check=True,
|
| 424 |
+
env={"SYSTEM_VERSION_COMPAT": "0"},
|
| 425 |
+
stdout=subprocess.PIPE,
|
| 426 |
+
text=True,
|
| 427 |
+
).stdout
|
| 428 |
+
version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
|
| 429 |
+
|
| 430 |
+
if arch is None:
|
| 431 |
+
arch = _mac_arch(cpu_arch)
|
| 432 |
+
|
| 433 |
+
if (10, 0) <= version < (11, 0):
|
| 434 |
+
# Prior to Mac OS 11, each yearly release of Mac OS bumped the
|
| 435 |
+
# "minor" version number. The major version was always 10.
|
| 436 |
+
major_version = 10
|
| 437 |
+
for minor_version in range(version[1], -1, -1):
|
| 438 |
+
compat_version = major_version, minor_version
|
| 439 |
+
binary_formats = _mac_binary_formats(compat_version, arch)
|
| 440 |
+
for binary_format in binary_formats:
|
| 441 |
+
yield f"macosx_{major_version}_{minor_version}_{binary_format}"
|
| 442 |
+
|
| 443 |
+
if version >= (11, 0):
|
| 444 |
+
# Starting with Mac OS 11, each yearly release bumps the major version
|
| 445 |
+
# number. The minor versions are now the midyear updates.
|
| 446 |
+
minor_version = 0
|
| 447 |
+
for major_version in range(version[0], 10, -1):
|
| 448 |
+
compat_version = major_version, minor_version
|
| 449 |
+
binary_formats = _mac_binary_formats(compat_version, arch)
|
| 450 |
+
for binary_format in binary_formats:
|
| 451 |
+
yield f"macosx_{major_version}_{minor_version}_{binary_format}"
|
| 452 |
+
|
| 453 |
+
if version >= (11, 0):
|
| 454 |
+
# Mac OS 11 on x86_64 is compatible with binaries from previous releases.
|
| 455 |
+
# Arm64 support was introduced in 11.0, so no Arm binaries from previous
|
| 456 |
+
# releases exist.
|
| 457 |
+
#
|
| 458 |
+
# However, the "universal2" binary format can have a
|
| 459 |
+
# macOS version earlier than 11.0 when the x86_64 part of the binary supports
|
| 460 |
+
# that version of macOS.
|
| 461 |
+
major_version = 10
|
| 462 |
+
if arch == "x86_64":
|
| 463 |
+
for minor_version in range(16, 3, -1):
|
| 464 |
+
compat_version = major_version, minor_version
|
| 465 |
+
binary_formats = _mac_binary_formats(compat_version, arch)
|
| 466 |
+
for binary_format in binary_formats:
|
| 467 |
+
yield f"macosx_{major_version}_{minor_version}_{binary_format}"
|
| 468 |
+
else:
|
| 469 |
+
for minor_version in range(16, 3, -1):
|
| 470 |
+
compat_version = major_version, minor_version
|
| 471 |
+
binary_format = "universal2"
|
| 472 |
+
yield f"macosx_{major_version}_{minor_version}_{binary_format}"
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
def ios_platforms(
|
| 476 |
+
version: AppleVersion | None = None, multiarch: str | None = None
|
| 477 |
+
) -> Iterator[str]:
|
| 478 |
+
"""
|
| 479 |
+
Yields the platform tags for an iOS system.
|
| 480 |
+
|
| 481 |
+
:param version: A two-item tuple specifying the iOS version to generate
|
| 482 |
+
platform tags for. Defaults to the current iOS version.
|
| 483 |
+
:param multiarch: The CPU architecture+ABI to generate platform tags for -
|
| 484 |
+
(the value used by `sys.implementation._multiarch` e.g.,
|
| 485 |
+
`arm64_iphoneos` or `x84_64_iphonesimulator`). Defaults to the current
|
| 486 |
+
multiarch value.
|
| 487 |
+
"""
|
| 488 |
+
if version is None:
|
| 489 |
+
# if iOS is the current platform, ios_ver *must* be defined. However,
|
| 490 |
+
# it won't exist for CPython versions before 3.13, which causes a mypy
|
| 491 |
+
# error.
|
| 492 |
+
_, release, _, _ = platform.ios_ver() # type: ignore[attr-defined, unused-ignore]
|
| 493 |
+
version = cast("AppleVersion", tuple(map(int, release.split(".")[:2])))
|
| 494 |
+
|
| 495 |
+
if multiarch is None:
|
| 496 |
+
multiarch = sys.implementation._multiarch
|
| 497 |
+
multiarch = multiarch.replace("-", "_")
|
| 498 |
+
|
| 499 |
+
ios_platform_template = "ios_{major}_{minor}_{multiarch}"
|
| 500 |
+
|
| 501 |
+
# Consider any iOS major.minor version from the version requested, down to
|
| 502 |
+
# 12.0. 12.0 is the first iOS version that is known to have enough features
|
| 503 |
+
# to support CPython. Consider every possible minor release up to X.9. There
|
| 504 |
+
# highest the minor has ever gone is 8 (14.8 and 15.8) but having some extra
|
| 505 |
+
# candidates that won't ever match doesn't really hurt, and it saves us from
|
| 506 |
+
# having to keep an explicit list of known iOS versions in the code. Return
|
| 507 |
+
# the results descending order of version number.
|
| 508 |
+
|
| 509 |
+
# If the requested major version is less than 12, there won't be any matches.
|
| 510 |
+
if version[0] < 12:
|
| 511 |
+
return
|
| 512 |
+
|
| 513 |
+
# Consider the actual X.Y version that was requested.
|
| 514 |
+
yield ios_platform_template.format(
|
| 515 |
+
major=version[0], minor=version[1], multiarch=multiarch
|
| 516 |
+
)
|
| 517 |
+
|
| 518 |
+
# Consider every minor version from X.0 to the minor version prior to the
|
| 519 |
+
# version requested by the platform.
|
| 520 |
+
for minor in range(version[1] - 1, -1, -1):
|
| 521 |
+
yield ios_platform_template.format(
|
| 522 |
+
major=version[0], minor=minor, multiarch=multiarch
|
| 523 |
+
)
|
| 524 |
+
|
| 525 |
+
for major in range(version[0] - 1, 11, -1):
|
| 526 |
+
for minor in range(9, -1, -1):
|
| 527 |
+
yield ios_platform_template.format(
|
| 528 |
+
major=major, minor=minor, multiarch=multiarch
|
| 529 |
+
)
|
| 530 |
+
|
| 531 |
+
|
| 532 |
+
def android_platforms(
|
| 533 |
+
api_level: int | None = None, abi: str | None = None
|
| 534 |
+
) -> Iterator[str]:
|
| 535 |
+
"""
|
| 536 |
+
Yields the :attr:`~Tag.platform` tags for Android. If this function is invoked on
|
| 537 |
+
non-Android platforms, the ``api_level`` and ``abi`` arguments are required.
|
| 538 |
+
|
| 539 |
+
:param int api_level: The maximum `API level
|
| 540 |
+
<https://developer.android.com/tools/releases/platforms>`__ to return. Defaults
|
| 541 |
+
to the current system's version, as returned by ``platform.android_ver``.
|
| 542 |
+
:param str abi: The `Android ABI <https://developer.android.com/ndk/guides/abis>`__,
|
| 543 |
+
e.g. ``arm64_v8a``. Defaults to the current system's ABI , as returned by
|
| 544 |
+
``sysconfig.get_platform``. Hyphens and periods will be replaced with
|
| 545 |
+
underscores.
|
| 546 |
+
"""
|
| 547 |
+
if platform.system() != "Android" and (api_level is None or abi is None):
|
| 548 |
+
raise TypeError(
|
| 549 |
+
"on non-Android platforms, the api_level and abi arguments are required"
|
| 550 |
+
)
|
| 551 |
+
|
| 552 |
+
if api_level is None:
|
| 553 |
+
# Python 3.13 was the first version to return platform.system() == "Android",
|
| 554 |
+
# and also the first version to define platform.android_ver().
|
| 555 |
+
api_level = platform.android_ver().api_level # type: ignore[attr-defined]
|
| 556 |
+
|
| 557 |
+
if abi is None:
|
| 558 |
+
abi = sysconfig.get_platform().split("-")[-1]
|
| 559 |
+
abi = _normalize_string(abi)
|
| 560 |
+
|
| 561 |
+
# 16 is the minimum API level known to have enough features to support CPython
|
| 562 |
+
# without major patching. Yield every API level from the maximum down to the
|
| 563 |
+
# minimum, inclusive.
|
| 564 |
+
min_api_level = 16
|
| 565 |
+
for ver in range(api_level, min_api_level - 1, -1):
|
| 566 |
+
yield f"android_{ver}_{abi}"
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
|
| 570 |
+
linux = _normalize_string(sysconfig.get_platform())
|
| 571 |
+
if not linux.startswith("linux_"):
|
| 572 |
+
# we should never be here, just yield the sysconfig one and return
|
| 573 |
+
yield linux
|
| 574 |
+
return
|
| 575 |
+
if is_32bit:
|
| 576 |
+
if linux == "linux_x86_64":
|
| 577 |
+
linux = "linux_i686"
|
| 578 |
+
elif linux == "linux_aarch64":
|
| 579 |
+
linux = "linux_armv8l"
|
| 580 |
+
_, arch = linux.split("_", 1)
|
| 581 |
+
archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch])
|
| 582 |
+
yield from _manylinux.platform_tags(archs)
|
| 583 |
+
yield from _musllinux.platform_tags(archs)
|
| 584 |
+
for arch in archs:
|
| 585 |
+
yield f"linux_{arch}"
|
| 586 |
+
|
| 587 |
+
|
| 588 |
+
def _generic_platforms() -> Iterator[str]:
|
| 589 |
+
yield _normalize_string(sysconfig.get_platform())
|
| 590 |
+
|
| 591 |
+
|
| 592 |
+
def platform_tags() -> Iterator[str]:
|
| 593 |
+
"""
|
| 594 |
+
Provides the platform tags for this installation.
|
| 595 |
+
"""
|
| 596 |
+
if platform.system() == "Darwin":
|
| 597 |
+
return mac_platforms()
|
| 598 |
+
elif platform.system() == "iOS":
|
| 599 |
+
return ios_platforms()
|
| 600 |
+
elif platform.system() == "Android":
|
| 601 |
+
return android_platforms()
|
| 602 |
+
elif platform.system() == "Linux":
|
| 603 |
+
return _linux_platforms()
|
| 604 |
+
else:
|
| 605 |
+
return _generic_platforms()
|
| 606 |
+
|
| 607 |
+
|
| 608 |
+
def interpreter_name() -> str:
|
| 609 |
+
"""
|
| 610 |
+
Returns the name of the running interpreter.
|
| 611 |
+
|
| 612 |
+
Some implementations have a reserved, two-letter abbreviation which will
|
| 613 |
+
be returned when appropriate.
|
| 614 |
+
"""
|
| 615 |
+
name = sys.implementation.name
|
| 616 |
+
return INTERPRETER_SHORT_NAMES.get(name) or name
|
| 617 |
+
|
| 618 |
+
|
| 619 |
+
def interpreter_version(*, warn: bool = False) -> str:
|
| 620 |
+
"""
|
| 621 |
+
Returns the version of the running interpreter.
|
| 622 |
+
"""
|
| 623 |
+
version = _get_config_var("py_version_nodot", warn=warn)
|
| 624 |
+
return str(version) if version else _version_nodot(sys.version_info[:2])
|
| 625 |
+
|
| 626 |
+
|
| 627 |
+
def _version_nodot(version: PythonVersion) -> str:
|
| 628 |
+
return "".join(map(str, version))
|
| 629 |
+
|
| 630 |
+
|
| 631 |
+
def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
|
| 632 |
+
"""
|
| 633 |
+
Returns the sequence of tag triples for the running interpreter.
|
| 634 |
+
|
| 635 |
+
The order of the sequence corresponds to priority order for the
|
| 636 |
+
interpreter, from most to least important.
|
| 637 |
+
"""
|
| 638 |
+
|
| 639 |
+
interp_name = interpreter_name()
|
| 640 |
+
if interp_name == "cp":
|
| 641 |
+
yield from cpython_tags(warn=warn)
|
| 642 |
+
else:
|
| 643 |
+
yield from generic_tags()
|
| 644 |
+
|
| 645 |
+
if interp_name == "pp":
|
| 646 |
+
interp = "pp3"
|
| 647 |
+
elif interp_name == "cp":
|
| 648 |
+
interp = "cp" + interpreter_version(warn=warn)
|
| 649 |
+
else:
|
| 650 |
+
interp = None
|
| 651 |
+
yield from compatible_tags(interpreter=interp)
|
.venv/lib/python3.13/site-packages/packaging/utils.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import re
|
| 8 |
+
from typing import NewType, Tuple, Union, cast
|
| 9 |
+
|
| 10 |
+
from .tags import Tag, parse_tag
|
| 11 |
+
from .version import InvalidVersion, Version, _TrimmedRelease
|
| 12 |
+
|
| 13 |
+
BuildTag = Union[Tuple[()], Tuple[int, str]]
|
| 14 |
+
NormalizedName = NewType("NormalizedName", str)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class InvalidName(ValueError):
|
| 18 |
+
"""
|
| 19 |
+
An invalid distribution name; users should refer to the packaging user guide.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class InvalidWheelFilename(ValueError):
|
| 24 |
+
"""
|
| 25 |
+
An invalid wheel filename was found, users should refer to PEP 427.
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class InvalidSdistFilename(ValueError):
|
| 30 |
+
"""
|
| 31 |
+
An invalid sdist filename was found, users should refer to the packaging user guide.
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# Core metadata spec for `Name`
|
| 36 |
+
_validate_regex = re.compile(r"[A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9]", re.IGNORECASE)
|
| 37 |
+
_normalized_regex = re.compile(r"[a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9]")
|
| 38 |
+
# PEP 427: The build number must start with a digit.
|
| 39 |
+
_build_tag_regex = re.compile(r"(\d+)(.*)")
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
|
| 43 |
+
if validate and not _validate_regex.fullmatch(name):
|
| 44 |
+
raise InvalidName(f"name is invalid: {name!r}")
|
| 45 |
+
# Ensure all ``.`` and ``_`` are ``-``
|
| 46 |
+
# Emulates ``re.sub(r"[-_.]+", "-", name).lower()`` from PEP 503
|
| 47 |
+
# Much faster than re, and even faster than str.translate
|
| 48 |
+
value = name.lower().replace("_", "-").replace(".", "-")
|
| 49 |
+
# Condense repeats (faster than regex)
|
| 50 |
+
while "--" in value:
|
| 51 |
+
value = value.replace("--", "-")
|
| 52 |
+
return cast("NormalizedName", value)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def is_normalized_name(name: str) -> bool:
|
| 56 |
+
return _normalized_regex.fullmatch(name) is not None
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def canonicalize_version(
|
| 60 |
+
version: Version | str, *, strip_trailing_zero: bool = True
|
| 61 |
+
) -> str:
|
| 62 |
+
"""
|
| 63 |
+
Return a canonical form of a version as a string.
|
| 64 |
+
|
| 65 |
+
>>> canonicalize_version('1.0.1')
|
| 66 |
+
'1.0.1'
|
| 67 |
+
|
| 68 |
+
Per PEP 625, versions may have multiple canonical forms, differing
|
| 69 |
+
only by trailing zeros.
|
| 70 |
+
|
| 71 |
+
>>> canonicalize_version('1.0.0')
|
| 72 |
+
'1'
|
| 73 |
+
>>> canonicalize_version('1.0.0', strip_trailing_zero=False)
|
| 74 |
+
'1.0.0'
|
| 75 |
+
|
| 76 |
+
Invalid versions are returned unaltered.
|
| 77 |
+
|
| 78 |
+
>>> canonicalize_version('foo bar baz')
|
| 79 |
+
'foo bar baz'
|
| 80 |
+
"""
|
| 81 |
+
if isinstance(version, str):
|
| 82 |
+
try:
|
| 83 |
+
version = Version(version)
|
| 84 |
+
except InvalidVersion:
|
| 85 |
+
return str(version)
|
| 86 |
+
return str(_TrimmedRelease(version) if strip_trailing_zero else version)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def parse_wheel_filename(
|
| 90 |
+
filename: str,
|
| 91 |
+
) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]:
|
| 92 |
+
if not filename.endswith(".whl"):
|
| 93 |
+
raise InvalidWheelFilename(
|
| 94 |
+
f"Invalid wheel filename (extension must be '.whl'): {filename!r}"
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
filename = filename[:-4]
|
| 98 |
+
dashes = filename.count("-")
|
| 99 |
+
if dashes not in (4, 5):
|
| 100 |
+
raise InvalidWheelFilename(
|
| 101 |
+
f"Invalid wheel filename (wrong number of parts): {filename!r}"
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
parts = filename.split("-", dashes - 2)
|
| 105 |
+
name_part = parts[0]
|
| 106 |
+
# See PEP 427 for the rules on escaping the project name.
|
| 107 |
+
if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
|
| 108 |
+
raise InvalidWheelFilename(f"Invalid project name: {filename!r}")
|
| 109 |
+
name = canonicalize_name(name_part)
|
| 110 |
+
|
| 111 |
+
try:
|
| 112 |
+
version = Version(parts[1])
|
| 113 |
+
except InvalidVersion as e:
|
| 114 |
+
raise InvalidWheelFilename(
|
| 115 |
+
f"Invalid wheel filename (invalid version): {filename!r}"
|
| 116 |
+
) from e
|
| 117 |
+
|
| 118 |
+
if dashes == 5:
|
| 119 |
+
build_part = parts[2]
|
| 120 |
+
build_match = _build_tag_regex.match(build_part)
|
| 121 |
+
if build_match is None:
|
| 122 |
+
raise InvalidWheelFilename(
|
| 123 |
+
f"Invalid build number: {build_part} in {filename!r}"
|
| 124 |
+
)
|
| 125 |
+
build = cast("BuildTag", (int(build_match.group(1)), build_match.group(2)))
|
| 126 |
+
else:
|
| 127 |
+
build = ()
|
| 128 |
+
tags = parse_tag(parts[-1])
|
| 129 |
+
return (name, version, build, tags)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]:
|
| 133 |
+
if filename.endswith(".tar.gz"):
|
| 134 |
+
file_stem = filename[: -len(".tar.gz")]
|
| 135 |
+
elif filename.endswith(".zip"):
|
| 136 |
+
file_stem = filename[: -len(".zip")]
|
| 137 |
+
else:
|
| 138 |
+
raise InvalidSdistFilename(
|
| 139 |
+
f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):"
|
| 140 |
+
f" {filename!r}"
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
# We are requiring a PEP 440 version, which cannot contain dashes,
|
| 144 |
+
# so we split on the last dash.
|
| 145 |
+
name_part, sep, version_part = file_stem.rpartition("-")
|
| 146 |
+
if not sep:
|
| 147 |
+
raise InvalidSdistFilename(f"Invalid sdist filename: {filename!r}")
|
| 148 |
+
|
| 149 |
+
name = canonicalize_name(name_part)
|
| 150 |
+
|
| 151 |
+
try:
|
| 152 |
+
version = Version(version_part)
|
| 153 |
+
except InvalidVersion as e:
|
| 154 |
+
raise InvalidSdistFilename(
|
| 155 |
+
f"Invalid sdist filename (invalid version): {filename!r}"
|
| 156 |
+
) from e
|
| 157 |
+
|
| 158 |
+
return (name, version)
|
.venv/lib/python3.13/site-packages/packaging/version.py
ADDED
|
@@ -0,0 +1,792 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
"""
|
| 5 |
+
.. testsetup::
|
| 6 |
+
|
| 7 |
+
from packaging.version import parse, Version
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import re
|
| 13 |
+
import sys
|
| 14 |
+
import typing
|
| 15 |
+
from typing import (
|
| 16 |
+
Any,
|
| 17 |
+
Callable,
|
| 18 |
+
Literal,
|
| 19 |
+
NamedTuple,
|
| 20 |
+
SupportsInt,
|
| 21 |
+
Tuple,
|
| 22 |
+
TypedDict,
|
| 23 |
+
Union,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
|
| 27 |
+
|
| 28 |
+
if typing.TYPE_CHECKING:
|
| 29 |
+
from typing_extensions import Self, Unpack
|
| 30 |
+
|
| 31 |
+
if sys.version_info >= (3, 13): # pragma: no cover
|
| 32 |
+
from warnings import deprecated as _deprecated
|
| 33 |
+
elif typing.TYPE_CHECKING:
|
| 34 |
+
from typing_extensions import deprecated as _deprecated
|
| 35 |
+
else: # pragma: no cover
|
| 36 |
+
import functools
|
| 37 |
+
import warnings
|
| 38 |
+
|
| 39 |
+
def _deprecated(message: str) -> object:
|
| 40 |
+
def decorator(func: object) -> object:
|
| 41 |
+
@functools.wraps(func)
|
| 42 |
+
def wrapper(*args: object, **kwargs: object) -> object:
|
| 43 |
+
warnings.warn(
|
| 44 |
+
message,
|
| 45 |
+
category=DeprecationWarning,
|
| 46 |
+
stacklevel=2,
|
| 47 |
+
)
|
| 48 |
+
return func(*args, **kwargs)
|
| 49 |
+
|
| 50 |
+
return wrapper
|
| 51 |
+
|
| 52 |
+
return decorator
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
_LETTER_NORMALIZATION = {
|
| 56 |
+
"alpha": "a",
|
| 57 |
+
"beta": "b",
|
| 58 |
+
"c": "rc",
|
| 59 |
+
"pre": "rc",
|
| 60 |
+
"preview": "rc",
|
| 61 |
+
"rev": "post",
|
| 62 |
+
"r": "post",
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"]
|
| 66 |
+
|
| 67 |
+
LocalType = Tuple[Union[int, str], ...]
|
| 68 |
+
|
| 69 |
+
CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]
|
| 70 |
+
CmpLocalType = Union[
|
| 71 |
+
NegativeInfinityType,
|
| 72 |
+
Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],
|
| 73 |
+
]
|
| 74 |
+
CmpKey = Tuple[
|
| 75 |
+
int,
|
| 76 |
+
Tuple[int, ...],
|
| 77 |
+
CmpPrePostDevType,
|
| 78 |
+
CmpPrePostDevType,
|
| 79 |
+
CmpPrePostDevType,
|
| 80 |
+
CmpLocalType,
|
| 81 |
+
]
|
| 82 |
+
VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
class _VersionReplace(TypedDict, total=False):
|
| 86 |
+
epoch: int | None
|
| 87 |
+
release: tuple[int, ...] | None
|
| 88 |
+
pre: tuple[Literal["a", "b", "rc"], int] | None
|
| 89 |
+
post: int | None
|
| 90 |
+
dev: int | None
|
| 91 |
+
local: str | None
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def parse(version: str) -> Version:
|
| 95 |
+
"""Parse the given version string.
|
| 96 |
+
|
| 97 |
+
>>> parse('1.0.dev1')
|
| 98 |
+
<Version('1.0.dev1')>
|
| 99 |
+
|
| 100 |
+
:param version: The version string to parse.
|
| 101 |
+
:raises InvalidVersion: When the version string is not a valid version.
|
| 102 |
+
"""
|
| 103 |
+
return Version(version)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
class InvalidVersion(ValueError):
|
| 107 |
+
"""Raised when a version string is not a valid version.
|
| 108 |
+
|
| 109 |
+
>>> Version("invalid")
|
| 110 |
+
Traceback (most recent call last):
|
| 111 |
+
...
|
| 112 |
+
packaging.version.InvalidVersion: Invalid version: 'invalid'
|
| 113 |
+
"""
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class _BaseVersion:
|
| 117 |
+
__slots__ = ()
|
| 118 |
+
|
| 119 |
+
# This can also be a normal member (see the packaging_legacy package);
|
| 120 |
+
# we are just requiring it to be readable. Actually defining a property
|
| 121 |
+
# has runtime effect on subclasses, so it's typing only.
|
| 122 |
+
if typing.TYPE_CHECKING:
|
| 123 |
+
|
| 124 |
+
@property
|
| 125 |
+
def _key(self) -> tuple[Any, ...]: ...
|
| 126 |
+
|
| 127 |
+
def __hash__(self) -> int:
|
| 128 |
+
return hash(self._key)
|
| 129 |
+
|
| 130 |
+
# Please keep the duplicated `isinstance` check
|
| 131 |
+
# in the six comparisons hereunder
|
| 132 |
+
# unless you find a way to avoid adding overhead function calls.
|
| 133 |
+
def __lt__(self, other: _BaseVersion) -> bool:
|
| 134 |
+
if not isinstance(other, _BaseVersion):
|
| 135 |
+
return NotImplemented
|
| 136 |
+
|
| 137 |
+
return self._key < other._key
|
| 138 |
+
|
| 139 |
+
def __le__(self, other: _BaseVersion) -> bool:
|
| 140 |
+
if not isinstance(other, _BaseVersion):
|
| 141 |
+
return NotImplemented
|
| 142 |
+
|
| 143 |
+
return self._key <= other._key
|
| 144 |
+
|
| 145 |
+
def __eq__(self, other: object) -> bool:
|
| 146 |
+
if not isinstance(other, _BaseVersion):
|
| 147 |
+
return NotImplemented
|
| 148 |
+
|
| 149 |
+
return self._key == other._key
|
| 150 |
+
|
| 151 |
+
def __ge__(self, other: _BaseVersion) -> bool:
|
| 152 |
+
if not isinstance(other, _BaseVersion):
|
| 153 |
+
return NotImplemented
|
| 154 |
+
|
| 155 |
+
return self._key >= other._key
|
| 156 |
+
|
| 157 |
+
def __gt__(self, other: _BaseVersion) -> bool:
|
| 158 |
+
if not isinstance(other, _BaseVersion):
|
| 159 |
+
return NotImplemented
|
| 160 |
+
|
| 161 |
+
return self._key > other._key
|
| 162 |
+
|
| 163 |
+
def __ne__(self, other: object) -> bool:
|
| 164 |
+
if not isinstance(other, _BaseVersion):
|
| 165 |
+
return NotImplemented
|
| 166 |
+
|
| 167 |
+
return self._key != other._key
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
# Deliberately not anchored to the start and end of the string, to make it
|
| 171 |
+
# easier for 3rd party code to reuse
|
| 172 |
+
|
| 173 |
+
# Note that ++ doesn't behave identically on CPython and PyPy, so not using it here
|
| 174 |
+
_VERSION_PATTERN = r"""
|
| 175 |
+
v?+ # optional leading v
|
| 176 |
+
(?:
|
| 177 |
+
(?:(?P<epoch>[0-9]+)!)?+ # epoch
|
| 178 |
+
(?P<release>[0-9]+(?:\.[0-9]+)*+) # release segment
|
| 179 |
+
(?P<pre> # pre-release
|
| 180 |
+
[._-]?+
|
| 181 |
+
(?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
|
| 182 |
+
[._-]?+
|
| 183 |
+
(?P<pre_n>[0-9]+)?
|
| 184 |
+
)?+
|
| 185 |
+
(?P<post> # post release
|
| 186 |
+
(?:-(?P<post_n1>[0-9]+))
|
| 187 |
+
|
|
| 188 |
+
(?:
|
| 189 |
+
[._-]?
|
| 190 |
+
(?P<post_l>post|rev|r)
|
| 191 |
+
[._-]?
|
| 192 |
+
(?P<post_n2>[0-9]+)?
|
| 193 |
+
)
|
| 194 |
+
)?+
|
| 195 |
+
(?P<dev> # dev release
|
| 196 |
+
[._-]?+
|
| 197 |
+
(?P<dev_l>dev)
|
| 198 |
+
[._-]?+
|
| 199 |
+
(?P<dev_n>[0-9]+)?
|
| 200 |
+
)?+
|
| 201 |
+
)
|
| 202 |
+
(?:\+
|
| 203 |
+
(?P<local> # local version
|
| 204 |
+
[a-z0-9]+
|
| 205 |
+
(?:[._-][a-z0-9]+)*+
|
| 206 |
+
)
|
| 207 |
+
)?+
|
| 208 |
+
"""
|
| 209 |
+
|
| 210 |
+
_VERSION_PATTERN_OLD = _VERSION_PATTERN.replace("*+", "*").replace("?+", "?")
|
| 211 |
+
|
| 212 |
+
# Possessive qualifiers were added in Python 3.11.
|
| 213 |
+
# CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795
|
| 214 |
+
# Older PyPy also had a bug.
|
| 215 |
+
VERSION_PATTERN = (
|
| 216 |
+
_VERSION_PATTERN_OLD
|
| 217 |
+
if (sys.implementation.name == "cpython" and sys.version_info < (3, 11, 5))
|
| 218 |
+
or (sys.implementation.name == "pypy" and sys.version_info < (3, 11, 13))
|
| 219 |
+
or sys.version_info < (3, 11)
|
| 220 |
+
else _VERSION_PATTERN
|
| 221 |
+
)
|
| 222 |
+
"""
|
| 223 |
+
A string containing the regular expression used to match a valid version.
|
| 224 |
+
|
| 225 |
+
The pattern is not anchored at either end, and is intended for embedding in larger
|
| 226 |
+
expressions (for example, matching a version number as part of a file name). The
|
| 227 |
+
regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
|
| 228 |
+
flags set.
|
| 229 |
+
|
| 230 |
+
:meta hide-value:
|
| 231 |
+
"""
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
# Validation pattern for local version in replace()
|
| 235 |
+
_LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def _validate_epoch(value: object, /) -> int:
|
| 239 |
+
epoch = value or 0
|
| 240 |
+
if isinstance(epoch, int) and epoch >= 0:
|
| 241 |
+
return epoch
|
| 242 |
+
msg = f"epoch must be non-negative integer, got {epoch}"
|
| 243 |
+
raise InvalidVersion(msg)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def _validate_release(value: object, /) -> tuple[int, ...]:
|
| 247 |
+
release = (0,) if value is None else value
|
| 248 |
+
if (
|
| 249 |
+
isinstance(release, tuple)
|
| 250 |
+
and len(release) > 0
|
| 251 |
+
and all(isinstance(i, int) and i >= 0 for i in release)
|
| 252 |
+
):
|
| 253 |
+
return release
|
| 254 |
+
msg = f"release must be a non-empty tuple of non-negative integers, got {release}"
|
| 255 |
+
raise InvalidVersion(msg)
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def _validate_pre(value: object, /) -> tuple[Literal["a", "b", "rc"], int] | None:
|
| 259 |
+
if value is None:
|
| 260 |
+
return value
|
| 261 |
+
if (
|
| 262 |
+
isinstance(value, tuple)
|
| 263 |
+
and len(value) == 2
|
| 264 |
+
and value[0] in ("a", "b", "rc")
|
| 265 |
+
and isinstance(value[1], int)
|
| 266 |
+
and value[1] >= 0
|
| 267 |
+
):
|
| 268 |
+
return value
|
| 269 |
+
msg = f"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}"
|
| 270 |
+
raise InvalidVersion(msg)
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None:
|
| 274 |
+
if value is None:
|
| 275 |
+
return value
|
| 276 |
+
if isinstance(value, int) and value >= 0:
|
| 277 |
+
return ("post", value)
|
| 278 |
+
msg = f"post must be non-negative integer, got {value}"
|
| 279 |
+
raise InvalidVersion(msg)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None:
|
| 283 |
+
if value is None:
|
| 284 |
+
return value
|
| 285 |
+
if isinstance(value, int) and value >= 0:
|
| 286 |
+
return ("dev", value)
|
| 287 |
+
msg = f"dev must be non-negative integer, got {value}"
|
| 288 |
+
raise InvalidVersion(msg)
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def _validate_local(value: object, /) -> LocalType | None:
|
| 292 |
+
if value is None:
|
| 293 |
+
return value
|
| 294 |
+
if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):
|
| 295 |
+
return _parse_local_version(value)
|
| 296 |
+
msg = f"local must be a valid version string, got {value!r}"
|
| 297 |
+
raise InvalidVersion(msg)
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
# Backward compatibility for internals before 26.0. Do not use.
|
| 301 |
+
class _Version(NamedTuple):
|
| 302 |
+
epoch: int
|
| 303 |
+
release: tuple[int, ...]
|
| 304 |
+
dev: tuple[str, int] | None
|
| 305 |
+
pre: tuple[str, int] | None
|
| 306 |
+
post: tuple[str, int] | None
|
| 307 |
+
local: LocalType | None
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
class Version(_BaseVersion):
|
| 311 |
+
"""This class abstracts handling of a project's versions.
|
| 312 |
+
|
| 313 |
+
A :class:`Version` instance is comparison aware and can be compared and
|
| 314 |
+
sorted using the standard Python interfaces.
|
| 315 |
+
|
| 316 |
+
>>> v1 = Version("1.0a5")
|
| 317 |
+
>>> v2 = Version("1.0")
|
| 318 |
+
>>> v1
|
| 319 |
+
<Version('1.0a5')>
|
| 320 |
+
>>> v2
|
| 321 |
+
<Version('1.0')>
|
| 322 |
+
>>> v1 < v2
|
| 323 |
+
True
|
| 324 |
+
>>> v1 == v2
|
| 325 |
+
False
|
| 326 |
+
>>> v1 > v2
|
| 327 |
+
False
|
| 328 |
+
>>> v1 >= v2
|
| 329 |
+
False
|
| 330 |
+
>>> v1 <= v2
|
| 331 |
+
True
|
| 332 |
+
"""
|
| 333 |
+
|
| 334 |
+
__slots__ = ("_dev", "_epoch", "_key_cache", "_local", "_post", "_pre", "_release")
|
| 335 |
+
__match_args__ = ("_str",)
|
| 336 |
+
|
| 337 |
+
_regex = re.compile(r"\s*" + VERSION_PATTERN + r"\s*", re.VERBOSE | re.IGNORECASE)
|
| 338 |
+
|
| 339 |
+
_epoch: int
|
| 340 |
+
_release: tuple[int, ...]
|
| 341 |
+
_dev: tuple[str, int] | None
|
| 342 |
+
_pre: tuple[str, int] | None
|
| 343 |
+
_post: tuple[str, int] | None
|
| 344 |
+
_local: LocalType | None
|
| 345 |
+
|
| 346 |
+
_key_cache: CmpKey | None
|
| 347 |
+
|
| 348 |
+
def __init__(self, version: str) -> None:
|
| 349 |
+
"""Initialize a Version object.
|
| 350 |
+
|
| 351 |
+
:param version:
|
| 352 |
+
The string representation of a version which will be parsed and normalized
|
| 353 |
+
before use.
|
| 354 |
+
:raises InvalidVersion:
|
| 355 |
+
If the ``version`` does not conform to PEP 440 in any way then this
|
| 356 |
+
exception will be raised.
|
| 357 |
+
"""
|
| 358 |
+
# Validate the version and parse it into pieces
|
| 359 |
+
match = self._regex.fullmatch(version)
|
| 360 |
+
if not match:
|
| 361 |
+
raise InvalidVersion(f"Invalid version: {version!r}")
|
| 362 |
+
self._epoch = int(match.group("epoch")) if match.group("epoch") else 0
|
| 363 |
+
self._release = tuple(map(int, match.group("release").split(".")))
|
| 364 |
+
self._pre = _parse_letter_version(match.group("pre_l"), match.group("pre_n"))
|
| 365 |
+
self._post = _parse_letter_version(
|
| 366 |
+
match.group("post_l"), match.group("post_n1") or match.group("post_n2")
|
| 367 |
+
)
|
| 368 |
+
self._dev = _parse_letter_version(match.group("dev_l"), match.group("dev_n"))
|
| 369 |
+
self._local = _parse_local_version(match.group("local"))
|
| 370 |
+
|
| 371 |
+
# Key which will be used for sorting
|
| 372 |
+
self._key_cache = None
|
| 373 |
+
|
| 374 |
+
def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:
|
| 375 |
+
epoch = _validate_epoch(kwargs["epoch"]) if "epoch" in kwargs else self._epoch
|
| 376 |
+
release = (
|
| 377 |
+
_validate_release(kwargs["release"])
|
| 378 |
+
if "release" in kwargs
|
| 379 |
+
else self._release
|
| 380 |
+
)
|
| 381 |
+
pre = _validate_pre(kwargs["pre"]) if "pre" in kwargs else self._pre
|
| 382 |
+
post = _validate_post(kwargs["post"]) if "post" in kwargs else self._post
|
| 383 |
+
dev = _validate_dev(kwargs["dev"]) if "dev" in kwargs else self._dev
|
| 384 |
+
local = _validate_local(kwargs["local"]) if "local" in kwargs else self._local
|
| 385 |
+
|
| 386 |
+
if (
|
| 387 |
+
epoch == self._epoch
|
| 388 |
+
and release == self._release
|
| 389 |
+
and pre == self._pre
|
| 390 |
+
and post == self._post
|
| 391 |
+
and dev == self._dev
|
| 392 |
+
and local == self._local
|
| 393 |
+
):
|
| 394 |
+
return self
|
| 395 |
+
|
| 396 |
+
new_version = self.__class__.__new__(self.__class__)
|
| 397 |
+
new_version._key_cache = None
|
| 398 |
+
new_version._epoch = epoch
|
| 399 |
+
new_version._release = release
|
| 400 |
+
new_version._pre = pre
|
| 401 |
+
new_version._post = post
|
| 402 |
+
new_version._dev = dev
|
| 403 |
+
new_version._local = local
|
| 404 |
+
|
| 405 |
+
return new_version
|
| 406 |
+
|
| 407 |
+
@property
|
| 408 |
+
def _key(self) -> CmpKey:
|
| 409 |
+
if self._key_cache is None:
|
| 410 |
+
self._key_cache = _cmpkey(
|
| 411 |
+
self._epoch,
|
| 412 |
+
self._release,
|
| 413 |
+
self._pre,
|
| 414 |
+
self._post,
|
| 415 |
+
self._dev,
|
| 416 |
+
self._local,
|
| 417 |
+
)
|
| 418 |
+
return self._key_cache
|
| 419 |
+
|
| 420 |
+
@property
|
| 421 |
+
@_deprecated("Version._version is private and will be removed soon")
|
| 422 |
+
def _version(self) -> _Version:
|
| 423 |
+
return _Version(
|
| 424 |
+
self._epoch, self._release, self._dev, self._pre, self._post, self._local
|
| 425 |
+
)
|
| 426 |
+
|
| 427 |
+
@_version.setter
|
| 428 |
+
@_deprecated("Version._version is private and will be removed soon")
|
| 429 |
+
def _version(self, value: _Version) -> None:
|
| 430 |
+
self._epoch = value.epoch
|
| 431 |
+
self._release = value.release
|
| 432 |
+
self._dev = value.dev
|
| 433 |
+
self._pre = value.pre
|
| 434 |
+
self._post = value.post
|
| 435 |
+
self._local = value.local
|
| 436 |
+
self._key_cache = None
|
| 437 |
+
|
| 438 |
+
def __repr__(self) -> str:
|
| 439 |
+
"""A representation of the Version that shows all internal state.
|
| 440 |
+
|
| 441 |
+
>>> Version('1.0.0')
|
| 442 |
+
<Version('1.0.0')>
|
| 443 |
+
"""
|
| 444 |
+
return f"<Version('{self}')>"
|
| 445 |
+
|
| 446 |
+
def __str__(self) -> str:
|
| 447 |
+
"""A string representation of the version that can be round-tripped.
|
| 448 |
+
|
| 449 |
+
>>> str(Version("1.0a5"))
|
| 450 |
+
'1.0a5'
|
| 451 |
+
"""
|
| 452 |
+
# This is a hot function, so not calling self.base_version
|
| 453 |
+
version = ".".join(map(str, self.release))
|
| 454 |
+
|
| 455 |
+
# Epoch
|
| 456 |
+
if self.epoch:
|
| 457 |
+
version = f"{self.epoch}!{version}"
|
| 458 |
+
|
| 459 |
+
# Pre-release
|
| 460 |
+
if self.pre is not None:
|
| 461 |
+
version += "".join(map(str, self.pre))
|
| 462 |
+
|
| 463 |
+
# Post-release
|
| 464 |
+
if self.post is not None:
|
| 465 |
+
version += f".post{self.post}"
|
| 466 |
+
|
| 467 |
+
# Development release
|
| 468 |
+
if self.dev is not None:
|
| 469 |
+
version += f".dev{self.dev}"
|
| 470 |
+
|
| 471 |
+
# Local version segment
|
| 472 |
+
if self.local is not None:
|
| 473 |
+
version += f"+{self.local}"
|
| 474 |
+
|
| 475 |
+
return version
|
| 476 |
+
|
| 477 |
+
@property
|
| 478 |
+
def _str(self) -> str:
|
| 479 |
+
"""Internal property for match_args"""
|
| 480 |
+
return str(self)
|
| 481 |
+
|
| 482 |
+
@property
|
| 483 |
+
def epoch(self) -> int:
|
| 484 |
+
"""The epoch of the version.
|
| 485 |
+
|
| 486 |
+
>>> Version("2.0.0").epoch
|
| 487 |
+
0
|
| 488 |
+
>>> Version("1!2.0.0").epoch
|
| 489 |
+
1
|
| 490 |
+
"""
|
| 491 |
+
return self._epoch
|
| 492 |
+
|
| 493 |
+
@property
|
| 494 |
+
def release(self) -> tuple[int, ...]:
|
| 495 |
+
"""The components of the "release" segment of the version.
|
| 496 |
+
|
| 497 |
+
>>> Version("1.2.3").release
|
| 498 |
+
(1, 2, 3)
|
| 499 |
+
>>> Version("2.0.0").release
|
| 500 |
+
(2, 0, 0)
|
| 501 |
+
>>> Version("1!2.0.0.post0").release
|
| 502 |
+
(2, 0, 0)
|
| 503 |
+
|
| 504 |
+
Includes trailing zeroes but not the epoch or any pre-release / development /
|
| 505 |
+
post-release suffixes.
|
| 506 |
+
"""
|
| 507 |
+
return self._release
|
| 508 |
+
|
| 509 |
+
@property
|
| 510 |
+
def pre(self) -> tuple[str, int] | None:
|
| 511 |
+
"""The pre-release segment of the version.
|
| 512 |
+
|
| 513 |
+
>>> print(Version("1.2.3").pre)
|
| 514 |
+
None
|
| 515 |
+
>>> Version("1.2.3a1").pre
|
| 516 |
+
('a', 1)
|
| 517 |
+
>>> Version("1.2.3b1").pre
|
| 518 |
+
('b', 1)
|
| 519 |
+
>>> Version("1.2.3rc1").pre
|
| 520 |
+
('rc', 1)
|
| 521 |
+
"""
|
| 522 |
+
return self._pre
|
| 523 |
+
|
| 524 |
+
@property
|
| 525 |
+
def post(self) -> int | None:
|
| 526 |
+
"""The post-release number of the version.
|
| 527 |
+
|
| 528 |
+
>>> print(Version("1.2.3").post)
|
| 529 |
+
None
|
| 530 |
+
>>> Version("1.2.3.post1").post
|
| 531 |
+
1
|
| 532 |
+
"""
|
| 533 |
+
return self._post[1] if self._post else None
|
| 534 |
+
|
| 535 |
+
@property
|
| 536 |
+
def dev(self) -> int | None:
|
| 537 |
+
"""The development number of the version.
|
| 538 |
+
|
| 539 |
+
>>> print(Version("1.2.3").dev)
|
| 540 |
+
None
|
| 541 |
+
>>> Version("1.2.3.dev1").dev
|
| 542 |
+
1
|
| 543 |
+
"""
|
| 544 |
+
return self._dev[1] if self._dev else None
|
| 545 |
+
|
| 546 |
+
@property
|
| 547 |
+
def local(self) -> str | None:
|
| 548 |
+
"""The local version segment of the version.
|
| 549 |
+
|
| 550 |
+
>>> print(Version("1.2.3").local)
|
| 551 |
+
None
|
| 552 |
+
>>> Version("1.2.3+abc").local
|
| 553 |
+
'abc'
|
| 554 |
+
"""
|
| 555 |
+
if self._local:
|
| 556 |
+
return ".".join(str(x) for x in self._local)
|
| 557 |
+
else:
|
| 558 |
+
return None
|
| 559 |
+
|
| 560 |
+
@property
|
| 561 |
+
def public(self) -> str:
|
| 562 |
+
"""The public portion of the version.
|
| 563 |
+
|
| 564 |
+
>>> Version("1.2.3").public
|
| 565 |
+
'1.2.3'
|
| 566 |
+
>>> Version("1.2.3+abc").public
|
| 567 |
+
'1.2.3'
|
| 568 |
+
>>> Version("1!1.2.3dev1+abc").public
|
| 569 |
+
'1!1.2.3.dev1'
|
| 570 |
+
"""
|
| 571 |
+
return str(self).split("+", 1)[0]
|
| 572 |
+
|
| 573 |
+
@property
|
| 574 |
+
def base_version(self) -> str:
|
| 575 |
+
"""The "base version" of the version.
|
| 576 |
+
|
| 577 |
+
>>> Version("1.2.3").base_version
|
| 578 |
+
'1.2.3'
|
| 579 |
+
>>> Version("1.2.3+abc").base_version
|
| 580 |
+
'1.2.3'
|
| 581 |
+
>>> Version("1!1.2.3dev1+abc").base_version
|
| 582 |
+
'1!1.2.3'
|
| 583 |
+
|
| 584 |
+
The "base version" is the public version of the project without any pre or post
|
| 585 |
+
release markers.
|
| 586 |
+
"""
|
| 587 |
+
release_segment = ".".join(map(str, self.release))
|
| 588 |
+
return f"{self.epoch}!{release_segment}" if self.epoch else release_segment
|
| 589 |
+
|
| 590 |
+
@property
|
| 591 |
+
def is_prerelease(self) -> bool:
|
| 592 |
+
"""Whether this version is a pre-release.
|
| 593 |
+
|
| 594 |
+
>>> Version("1.2.3").is_prerelease
|
| 595 |
+
False
|
| 596 |
+
>>> Version("1.2.3a1").is_prerelease
|
| 597 |
+
True
|
| 598 |
+
>>> Version("1.2.3b1").is_prerelease
|
| 599 |
+
True
|
| 600 |
+
>>> Version("1.2.3rc1").is_prerelease
|
| 601 |
+
True
|
| 602 |
+
>>> Version("1.2.3dev1").is_prerelease
|
| 603 |
+
True
|
| 604 |
+
"""
|
| 605 |
+
return self.dev is not None or self.pre is not None
|
| 606 |
+
|
| 607 |
+
@property
|
| 608 |
+
def is_postrelease(self) -> bool:
|
| 609 |
+
"""Whether this version is a post-release.
|
| 610 |
+
|
| 611 |
+
>>> Version("1.2.3").is_postrelease
|
| 612 |
+
False
|
| 613 |
+
>>> Version("1.2.3.post1").is_postrelease
|
| 614 |
+
True
|
| 615 |
+
"""
|
| 616 |
+
return self.post is not None
|
| 617 |
+
|
| 618 |
+
@property
|
| 619 |
+
def is_devrelease(self) -> bool:
|
| 620 |
+
"""Whether this version is a development release.
|
| 621 |
+
|
| 622 |
+
>>> Version("1.2.3").is_devrelease
|
| 623 |
+
False
|
| 624 |
+
>>> Version("1.2.3.dev1").is_devrelease
|
| 625 |
+
True
|
| 626 |
+
"""
|
| 627 |
+
return self.dev is not None
|
| 628 |
+
|
| 629 |
+
@property
|
| 630 |
+
def major(self) -> int:
|
| 631 |
+
"""The first item of :attr:`release` or ``0`` if unavailable.
|
| 632 |
+
|
| 633 |
+
>>> Version("1.2.3").major
|
| 634 |
+
1
|
| 635 |
+
"""
|
| 636 |
+
return self.release[0] if len(self.release) >= 1 else 0
|
| 637 |
+
|
| 638 |
+
@property
|
| 639 |
+
def minor(self) -> int:
|
| 640 |
+
"""The second item of :attr:`release` or ``0`` if unavailable.
|
| 641 |
+
|
| 642 |
+
>>> Version("1.2.3").minor
|
| 643 |
+
2
|
| 644 |
+
>>> Version("1").minor
|
| 645 |
+
0
|
| 646 |
+
"""
|
| 647 |
+
return self.release[1] if len(self.release) >= 2 else 0
|
| 648 |
+
|
| 649 |
+
@property
|
| 650 |
+
def micro(self) -> int:
|
| 651 |
+
"""The third item of :attr:`release` or ``0`` if unavailable.
|
| 652 |
+
|
| 653 |
+
>>> Version("1.2.3").micro
|
| 654 |
+
3
|
| 655 |
+
>>> Version("1").micro
|
| 656 |
+
0
|
| 657 |
+
"""
|
| 658 |
+
return self.release[2] if len(self.release) >= 3 else 0
|
| 659 |
+
|
| 660 |
+
|
| 661 |
+
class _TrimmedRelease(Version):
|
| 662 |
+
__slots__ = ()
|
| 663 |
+
|
| 664 |
+
def __init__(self, version: str | Version) -> None:
|
| 665 |
+
if isinstance(version, Version):
|
| 666 |
+
self._epoch = version._epoch
|
| 667 |
+
self._release = version._release
|
| 668 |
+
self._dev = version._dev
|
| 669 |
+
self._pre = version._pre
|
| 670 |
+
self._post = version._post
|
| 671 |
+
self._local = version._local
|
| 672 |
+
self._key_cache = version._key_cache
|
| 673 |
+
return
|
| 674 |
+
super().__init__(version) # pragma: no cover
|
| 675 |
+
|
| 676 |
+
@property
|
| 677 |
+
def release(self) -> tuple[int, ...]:
|
| 678 |
+
"""
|
| 679 |
+
Release segment without any trailing zeros.
|
| 680 |
+
|
| 681 |
+
>>> _TrimmedRelease('1.0.0').release
|
| 682 |
+
(1,)
|
| 683 |
+
>>> _TrimmedRelease('0.0').release
|
| 684 |
+
(0,)
|
| 685 |
+
"""
|
| 686 |
+
# This leaves one 0.
|
| 687 |
+
rel = super().release
|
| 688 |
+
len_release = len(rel)
|
| 689 |
+
i = len_release
|
| 690 |
+
while i > 1 and rel[i - 1] == 0:
|
| 691 |
+
i -= 1
|
| 692 |
+
return rel if i == len_release else rel[:i]
|
| 693 |
+
|
| 694 |
+
|
| 695 |
+
def _parse_letter_version(
|
| 696 |
+
letter: str | None, number: str | bytes | SupportsInt | None
|
| 697 |
+
) -> tuple[str, int] | None:
|
| 698 |
+
if letter:
|
| 699 |
+
# We normalize any letters to their lower case form
|
| 700 |
+
letter = letter.lower()
|
| 701 |
+
|
| 702 |
+
# We consider some words to be alternate spellings of other words and
|
| 703 |
+
# in those cases we want to normalize the spellings to our preferred
|
| 704 |
+
# spelling.
|
| 705 |
+
letter = _LETTER_NORMALIZATION.get(letter, letter)
|
| 706 |
+
|
| 707 |
+
# We consider there to be an implicit 0 in a pre-release if there is
|
| 708 |
+
# not a numeral associated with it.
|
| 709 |
+
return letter, int(number or 0)
|
| 710 |
+
|
| 711 |
+
if number:
|
| 712 |
+
# We assume if we are given a number, but we are not given a letter
|
| 713 |
+
# then this is using the implicit post release syntax (e.g. 1.0-1)
|
| 714 |
+
return "post", int(number)
|
| 715 |
+
|
| 716 |
+
return None
|
| 717 |
+
|
| 718 |
+
|
| 719 |
+
_local_version_separators = re.compile(r"[\._-]")
|
| 720 |
+
|
| 721 |
+
|
| 722 |
+
def _parse_local_version(local: str | None) -> LocalType | None:
|
| 723 |
+
"""
|
| 724 |
+
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
|
| 725 |
+
"""
|
| 726 |
+
if local is not None:
|
| 727 |
+
return tuple(
|
| 728 |
+
part.lower() if not part.isdigit() else int(part)
|
| 729 |
+
for part in _local_version_separators.split(local)
|
| 730 |
+
)
|
| 731 |
+
return None
|
| 732 |
+
|
| 733 |
+
|
| 734 |
+
def _cmpkey(
|
| 735 |
+
epoch: int,
|
| 736 |
+
release: tuple[int, ...],
|
| 737 |
+
pre: tuple[str, int] | None,
|
| 738 |
+
post: tuple[str, int] | None,
|
| 739 |
+
dev: tuple[str, int] | None,
|
| 740 |
+
local: LocalType | None,
|
| 741 |
+
) -> CmpKey:
|
| 742 |
+
# When we compare a release version, we want to compare it with all of the
|
| 743 |
+
# trailing zeros removed. We will use this for our sorting key.
|
| 744 |
+
len_release = len(release)
|
| 745 |
+
i = len_release
|
| 746 |
+
while i and release[i - 1] == 0:
|
| 747 |
+
i -= 1
|
| 748 |
+
_release = release if i == len_release else release[:i]
|
| 749 |
+
|
| 750 |
+
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
|
| 751 |
+
# We'll do this by abusing the pre segment, but we _only_ want to do this
|
| 752 |
+
# if there is not a pre or a post segment. If we have one of those then
|
| 753 |
+
# the normal sorting rules will handle this case correctly.
|
| 754 |
+
if pre is None and post is None and dev is not None:
|
| 755 |
+
_pre: CmpPrePostDevType = NegativeInfinity
|
| 756 |
+
# Versions without a pre-release (except as noted above) should sort after
|
| 757 |
+
# those with one.
|
| 758 |
+
elif pre is None:
|
| 759 |
+
_pre = Infinity
|
| 760 |
+
else:
|
| 761 |
+
_pre = pre
|
| 762 |
+
|
| 763 |
+
# Versions without a post segment should sort before those with one.
|
| 764 |
+
if post is None:
|
| 765 |
+
_post: CmpPrePostDevType = NegativeInfinity
|
| 766 |
+
|
| 767 |
+
else:
|
| 768 |
+
_post = post
|
| 769 |
+
|
| 770 |
+
# Versions without a development segment should sort after those with one.
|
| 771 |
+
if dev is None:
|
| 772 |
+
_dev: CmpPrePostDevType = Infinity
|
| 773 |
+
|
| 774 |
+
else:
|
| 775 |
+
_dev = dev
|
| 776 |
+
|
| 777 |
+
if local is None:
|
| 778 |
+
# Versions without a local segment should sort before those with one.
|
| 779 |
+
_local: CmpLocalType = NegativeInfinity
|
| 780 |
+
else:
|
| 781 |
+
# Versions with a local segment need that segment parsed to implement
|
| 782 |
+
# the sorting rules in PEP440.
|
| 783 |
+
# - Alpha numeric segments sort before numeric segments
|
| 784 |
+
# - Alpha numeric segments sort lexicographically
|
| 785 |
+
# - Numeric segments sort numerically
|
| 786 |
+
# - Shorter versions sort before longer versions when the prefixes
|
| 787 |
+
# match exactly
|
| 788 |
+
_local = tuple(
|
| 789 |
+
(i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
|
| 790 |
+
)
|
| 791 |
+
|
| 792 |
+
return epoch, _release, _pre, _post, _dev, _local
|
.venv/lib/python3.13/site-packages/sympy/printing/__init__.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Printing subsystem"""
|
| 2 |
+
|
| 3 |
+
from .pretty import pager_print, pretty, pretty_print, pprint, pprint_use_unicode, pprint_try_use_unicode
|
| 4 |
+
|
| 5 |
+
from .latex import latex, print_latex, multiline_latex
|
| 6 |
+
|
| 7 |
+
from .mathml import mathml, print_mathml
|
| 8 |
+
|
| 9 |
+
from .python import python, print_python
|
| 10 |
+
|
| 11 |
+
from .pycode import pycode
|
| 12 |
+
|
| 13 |
+
from .codeprinter import print_ccode, print_fcode
|
| 14 |
+
|
| 15 |
+
from .codeprinter import ccode, fcode, cxxcode, rust_code # noqa:F811
|
| 16 |
+
|
| 17 |
+
from .smtlib import smtlib_code
|
| 18 |
+
|
| 19 |
+
from .glsl import glsl_code, print_glsl
|
| 20 |
+
|
| 21 |
+
from .rcode import rcode, print_rcode
|
| 22 |
+
|
| 23 |
+
from .jscode import jscode, print_jscode
|
| 24 |
+
|
| 25 |
+
from .julia import julia_code
|
| 26 |
+
|
| 27 |
+
from .mathematica import mathematica_code
|
| 28 |
+
|
| 29 |
+
from .octave import octave_code
|
| 30 |
+
|
| 31 |
+
from .gtk import print_gtk
|
| 32 |
+
|
| 33 |
+
from .preview import preview
|
| 34 |
+
|
| 35 |
+
from .repr import srepr
|
| 36 |
+
|
| 37 |
+
from .tree import print_tree
|
| 38 |
+
|
| 39 |
+
from .str import StrPrinter, sstr, sstrrepr
|
| 40 |
+
|
| 41 |
+
from .tableform import TableForm
|
| 42 |
+
|
| 43 |
+
from .dot import dotprint
|
| 44 |
+
|
| 45 |
+
from .maple import maple_code, print_maple_code
|
| 46 |
+
|
| 47 |
+
__all__ = [
|
| 48 |
+
# sympy.printing.pretty
|
| 49 |
+
'pager_print', 'pretty', 'pretty_print', 'pprint', 'pprint_use_unicode',
|
| 50 |
+
'pprint_try_use_unicode',
|
| 51 |
+
|
| 52 |
+
# sympy.printing.latex
|
| 53 |
+
'latex', 'print_latex', 'multiline_latex',
|
| 54 |
+
|
| 55 |
+
# sympy.printing.mathml
|
| 56 |
+
'mathml', 'print_mathml',
|
| 57 |
+
|
| 58 |
+
# sympy.printing.python
|
| 59 |
+
'python', 'print_python',
|
| 60 |
+
|
| 61 |
+
# sympy.printing.pycode
|
| 62 |
+
'pycode',
|
| 63 |
+
|
| 64 |
+
# sympy.printing.codeprinter
|
| 65 |
+
'ccode', 'print_ccode', 'cxxcode', 'fcode', 'print_fcode', 'rust_code',
|
| 66 |
+
|
| 67 |
+
# sympy.printing.smtlib
|
| 68 |
+
'smtlib_code',
|
| 69 |
+
|
| 70 |
+
# sympy.printing.glsl
|
| 71 |
+
'glsl_code', 'print_glsl',
|
| 72 |
+
|
| 73 |
+
# sympy.printing.rcode
|
| 74 |
+
'rcode', 'print_rcode',
|
| 75 |
+
|
| 76 |
+
# sympy.printing.jscode
|
| 77 |
+
'jscode', 'print_jscode',
|
| 78 |
+
|
| 79 |
+
# sympy.printing.julia
|
| 80 |
+
'julia_code',
|
| 81 |
+
|
| 82 |
+
# sympy.printing.mathematica
|
| 83 |
+
'mathematica_code',
|
| 84 |
+
|
| 85 |
+
# sympy.printing.octave
|
| 86 |
+
'octave_code',
|
| 87 |
+
|
| 88 |
+
# sympy.printing.gtk
|
| 89 |
+
'print_gtk',
|
| 90 |
+
|
| 91 |
+
# sympy.printing.preview
|
| 92 |
+
'preview',
|
| 93 |
+
|
| 94 |
+
# sympy.printing.repr
|
| 95 |
+
'srepr',
|
| 96 |
+
|
| 97 |
+
# sympy.printing.tree
|
| 98 |
+
'print_tree',
|
| 99 |
+
|
| 100 |
+
# sympy.printing.str
|
| 101 |
+
'StrPrinter', 'sstr', 'sstrrepr',
|
| 102 |
+
|
| 103 |
+
# sympy.printing.tableform
|
| 104 |
+
'TableForm',
|
| 105 |
+
|
| 106 |
+
# sympy.printing.dot
|
| 107 |
+
'dotprint',
|
| 108 |
+
|
| 109 |
+
# sympy.printing.maple
|
| 110 |
+
'maple_code', 'print_maple_code',
|
| 111 |
+
]
|
.venv/lib/python3.13/site-packages/sympy/printing/codeprinter.py
ADDED
|
@@ -0,0 +1,1039 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
from typing import Any
|
| 3 |
+
|
| 4 |
+
from functools import wraps
|
| 5 |
+
|
| 6 |
+
from sympy.core import Add, Mul, Pow, S, sympify, Float
|
| 7 |
+
from sympy.core.basic import Basic
|
| 8 |
+
from sympy.core.expr import Expr, UnevaluatedExpr
|
| 9 |
+
from sympy.core.function import Lambda
|
| 10 |
+
from sympy.core.mul import _keep_coeff
|
| 11 |
+
from sympy.core.sorting import default_sort_key
|
| 12 |
+
from sympy.core.symbol import Symbol
|
| 13 |
+
from sympy.functions.elementary.complexes import re
|
| 14 |
+
from sympy.printing.str import StrPrinter
|
| 15 |
+
from sympy.printing.precedence import precedence, PRECEDENCE
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class requires:
|
| 19 |
+
""" Decorator for registering requirements on print methods. """
|
| 20 |
+
def __init__(self, **kwargs):
|
| 21 |
+
self._req = kwargs
|
| 22 |
+
|
| 23 |
+
def __call__(self, method):
|
| 24 |
+
def _method_wrapper(self_, *args, **kwargs):
|
| 25 |
+
for k, v in self._req.items():
|
| 26 |
+
getattr(self_, k).update(v)
|
| 27 |
+
return method(self_, *args, **kwargs)
|
| 28 |
+
return wraps(method)(_method_wrapper)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class AssignmentError(Exception):
|
| 32 |
+
"""
|
| 33 |
+
Raised if an assignment variable for a loop is missing.
|
| 34 |
+
"""
|
| 35 |
+
pass
|
| 36 |
+
|
| 37 |
+
class PrintMethodNotImplementedError(NotImplementedError):
|
| 38 |
+
"""
|
| 39 |
+
Raised if a _print_* method is missing in the Printer.
|
| 40 |
+
"""
|
| 41 |
+
pass
|
| 42 |
+
|
| 43 |
+
def _convert_python_lists(arg):
|
| 44 |
+
if isinstance(arg, list):
|
| 45 |
+
from sympy.codegen.abstract_nodes import List
|
| 46 |
+
return List(*(_convert_python_lists(e) for e in arg))
|
| 47 |
+
elif isinstance(arg, tuple):
|
| 48 |
+
return tuple(_convert_python_lists(e) for e in arg)
|
| 49 |
+
else:
|
| 50 |
+
return arg
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class CodePrinter(StrPrinter):
|
| 54 |
+
"""
|
| 55 |
+
The base class for code-printing subclasses.
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
_operators = {
|
| 59 |
+
'and': '&&',
|
| 60 |
+
'or': '||',
|
| 61 |
+
'not': '!',
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
_default_settings: dict[str, Any] = {
|
| 65 |
+
'order': None,
|
| 66 |
+
'full_prec': 'auto',
|
| 67 |
+
'error_on_reserved': False,
|
| 68 |
+
'reserved_word_suffix': '_',
|
| 69 |
+
'human': True,
|
| 70 |
+
'inline': False,
|
| 71 |
+
'allow_unknown_functions': False,
|
| 72 |
+
'strict': None # True or False; None => True if human == True
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
# Functions which are "simple" to rewrite to other functions that
|
| 76 |
+
# may be supported
|
| 77 |
+
# function_to_rewrite : (function_to_rewrite_to, iterable_with_other_functions_required)
|
| 78 |
+
_rewriteable_functions = {
|
| 79 |
+
'cot': ('tan', []),
|
| 80 |
+
'csc': ('sin', []),
|
| 81 |
+
'sec': ('cos', []),
|
| 82 |
+
'acot': ('atan', []),
|
| 83 |
+
'acsc': ('asin', []),
|
| 84 |
+
'asec': ('acos', []),
|
| 85 |
+
'coth': ('exp', []),
|
| 86 |
+
'csch': ('exp', []),
|
| 87 |
+
'sech': ('exp', []),
|
| 88 |
+
'acoth': ('log', []),
|
| 89 |
+
'acsch': ('log', []),
|
| 90 |
+
'asech': ('log', []),
|
| 91 |
+
'catalan': ('gamma', []),
|
| 92 |
+
'fibonacci': ('sqrt', []),
|
| 93 |
+
'lucas': ('sqrt', []),
|
| 94 |
+
'beta': ('gamma', []),
|
| 95 |
+
'sinc': ('sin', ['Piecewise']),
|
| 96 |
+
'Mod': ('floor', []),
|
| 97 |
+
'factorial': ('gamma', []),
|
| 98 |
+
'factorial2': ('gamma', ['Piecewise']),
|
| 99 |
+
'subfactorial': ('uppergamma', []),
|
| 100 |
+
'RisingFactorial': ('gamma', ['Piecewise']),
|
| 101 |
+
'FallingFactorial': ('gamma', ['Piecewise']),
|
| 102 |
+
'binomial': ('gamma', []),
|
| 103 |
+
'frac': ('floor', []),
|
| 104 |
+
'Max': ('Piecewise', []),
|
| 105 |
+
'Min': ('Piecewise', []),
|
| 106 |
+
'Heaviside': ('Piecewise', []),
|
| 107 |
+
'erf2': ('erf', []),
|
| 108 |
+
'erfc': ('erf', []),
|
| 109 |
+
'Li': ('li', []),
|
| 110 |
+
'Ei': ('li', []),
|
| 111 |
+
'dirichlet_eta': ('zeta', []),
|
| 112 |
+
'riemann_xi': ('zeta', ['gamma']),
|
| 113 |
+
'SingularityFunction': ('Piecewise', []),
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
def __init__(self, settings=None):
|
| 117 |
+
super().__init__(settings=settings)
|
| 118 |
+
if self._settings.get('strict', True) == None:
|
| 119 |
+
# for backwards compatibility, human=False need not to throw:
|
| 120 |
+
self._settings['strict'] = self._settings.get('human', True) == True
|
| 121 |
+
if not hasattr(self, 'reserved_words'):
|
| 122 |
+
self.reserved_words = set()
|
| 123 |
+
|
| 124 |
+
def _handle_UnevaluatedExpr(self, expr):
|
| 125 |
+
return expr.replace(re, lambda arg: arg if isinstance(
|
| 126 |
+
arg, UnevaluatedExpr) and arg.args[0].is_real else re(arg))
|
| 127 |
+
|
| 128 |
+
def doprint(self, expr, assign_to=None):
|
| 129 |
+
"""
|
| 130 |
+
Print the expression as code.
|
| 131 |
+
|
| 132 |
+
Parameters
|
| 133 |
+
----------
|
| 134 |
+
expr : Expression
|
| 135 |
+
The expression to be printed.
|
| 136 |
+
|
| 137 |
+
assign_to : Symbol, string, MatrixSymbol, list of strings or Symbols (optional)
|
| 138 |
+
If provided, the printed code will set the expression to a variable or multiple variables
|
| 139 |
+
with the name or names given in ``assign_to``.
|
| 140 |
+
"""
|
| 141 |
+
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
| 142 |
+
from sympy.codegen.ast import CodeBlock, Assignment
|
| 143 |
+
|
| 144 |
+
def _handle_assign_to(expr, assign_to):
|
| 145 |
+
if assign_to is None:
|
| 146 |
+
return sympify(expr)
|
| 147 |
+
if isinstance(assign_to, (list, tuple)):
|
| 148 |
+
if len(expr) != len(assign_to):
|
| 149 |
+
raise ValueError('Failed to assign an expression of length {} to {} variables'.format(len(expr), len(assign_to)))
|
| 150 |
+
return CodeBlock(*[_handle_assign_to(lhs, rhs) for lhs, rhs in zip(expr, assign_to)])
|
| 151 |
+
if isinstance(assign_to, str):
|
| 152 |
+
if expr.is_Matrix:
|
| 153 |
+
assign_to = MatrixSymbol(assign_to, *expr.shape)
|
| 154 |
+
else:
|
| 155 |
+
assign_to = Symbol(assign_to)
|
| 156 |
+
elif not isinstance(assign_to, Basic):
|
| 157 |
+
raise TypeError("{} cannot assign to object of type {}".format(
|
| 158 |
+
type(self).__name__, type(assign_to)))
|
| 159 |
+
return Assignment(assign_to, expr)
|
| 160 |
+
|
| 161 |
+
expr = _convert_python_lists(expr)
|
| 162 |
+
expr = _handle_assign_to(expr, assign_to)
|
| 163 |
+
|
| 164 |
+
# Remove re(...) nodes due to UnevaluatedExpr.is_real always is None:
|
| 165 |
+
expr = self._handle_UnevaluatedExpr(expr)
|
| 166 |
+
|
| 167 |
+
# keep a set of expressions that are not strictly translatable to Code
|
| 168 |
+
# and number constants that must be declared and initialized
|
| 169 |
+
self._not_supported = set()
|
| 170 |
+
self._number_symbols = set()
|
| 171 |
+
|
| 172 |
+
lines = self._print(expr).splitlines()
|
| 173 |
+
|
| 174 |
+
# format the output
|
| 175 |
+
if self._settings["human"]:
|
| 176 |
+
frontlines = []
|
| 177 |
+
if self._not_supported:
|
| 178 |
+
frontlines.append(self._get_comment(
|
| 179 |
+
"Not supported in {}:".format(self.language)))
|
| 180 |
+
for expr in sorted(self._not_supported, key=str):
|
| 181 |
+
frontlines.append(self._get_comment(type(expr).__name__))
|
| 182 |
+
for name, value in sorted(self._number_symbols, key=str):
|
| 183 |
+
frontlines.append(self._declare_number_const(name, value))
|
| 184 |
+
lines = frontlines + lines
|
| 185 |
+
lines = self._format_code(lines)
|
| 186 |
+
result = "\n".join(lines)
|
| 187 |
+
else:
|
| 188 |
+
lines = self._format_code(lines)
|
| 189 |
+
num_syms = {(k, self._print(v)) for k, v in self._number_symbols}
|
| 190 |
+
result = (num_syms, self._not_supported, "\n".join(lines))
|
| 191 |
+
self._not_supported = set()
|
| 192 |
+
self._number_symbols = set()
|
| 193 |
+
return result
|
| 194 |
+
|
| 195 |
+
def _doprint_loops(self, expr, assign_to=None):
|
| 196 |
+
# Here we print an expression that contains Indexed objects, they
|
| 197 |
+
# correspond to arrays in the generated code. The low-level implementation
|
| 198 |
+
# involves looping over array elements and possibly storing results in temporary
|
| 199 |
+
# variables or accumulate it in the assign_to object.
|
| 200 |
+
|
| 201 |
+
if self._settings.get('contract', True):
|
| 202 |
+
from sympy.tensor import get_contraction_structure
|
| 203 |
+
# Setup loops over non-dummy indices -- all terms need these
|
| 204 |
+
indices = self._get_expression_indices(expr, assign_to)
|
| 205 |
+
# Setup loops over dummy indices -- each term needs separate treatment
|
| 206 |
+
dummies = get_contraction_structure(expr)
|
| 207 |
+
else:
|
| 208 |
+
indices = []
|
| 209 |
+
dummies = {None: (expr,)}
|
| 210 |
+
openloop, closeloop = self._get_loop_opening_ending(indices)
|
| 211 |
+
|
| 212 |
+
# terms with no summations first
|
| 213 |
+
if None in dummies:
|
| 214 |
+
text = StrPrinter.doprint(self, Add(*dummies[None]))
|
| 215 |
+
else:
|
| 216 |
+
# If all terms have summations we must initialize array to Zero
|
| 217 |
+
text = StrPrinter.doprint(self, 0)
|
| 218 |
+
|
| 219 |
+
# skip redundant assignments (where lhs == rhs)
|
| 220 |
+
lhs_printed = self._print(assign_to)
|
| 221 |
+
lines = []
|
| 222 |
+
if text != lhs_printed:
|
| 223 |
+
lines.extend(openloop)
|
| 224 |
+
if assign_to is not None:
|
| 225 |
+
text = self._get_statement("%s = %s" % (lhs_printed, text))
|
| 226 |
+
lines.append(text)
|
| 227 |
+
lines.extend(closeloop)
|
| 228 |
+
|
| 229 |
+
# then terms with summations
|
| 230 |
+
for d in dummies:
|
| 231 |
+
if isinstance(d, tuple):
|
| 232 |
+
indices = self._sort_optimized(d, expr)
|
| 233 |
+
openloop_d, closeloop_d = self._get_loop_opening_ending(
|
| 234 |
+
indices)
|
| 235 |
+
|
| 236 |
+
for term in dummies[d]:
|
| 237 |
+
if term in dummies and not ([list(f.keys()) for f in dummies[term]]
|
| 238 |
+
== [[None] for f in dummies[term]]):
|
| 239 |
+
# If one factor in the term has it's own internal
|
| 240 |
+
# contractions, those must be computed first.
|
| 241 |
+
# (temporary variables?)
|
| 242 |
+
raise NotImplementedError(
|
| 243 |
+
"FIXME: no support for contractions in factor yet")
|
| 244 |
+
else:
|
| 245 |
+
|
| 246 |
+
# We need the lhs expression as an accumulator for
|
| 247 |
+
# the loops, i.e
|
| 248 |
+
#
|
| 249 |
+
# for (int d=0; d < dim; d++){
|
| 250 |
+
# lhs[] = lhs[] + term[][d]
|
| 251 |
+
# } ^.................. the accumulator
|
| 252 |
+
#
|
| 253 |
+
# We check if the expression already contains the
|
| 254 |
+
# lhs, and raise an exception if it does, as that
|
| 255 |
+
# syntax is currently undefined. FIXME: What would be
|
| 256 |
+
# a good interpretation?
|
| 257 |
+
if assign_to is None:
|
| 258 |
+
raise AssignmentError(
|
| 259 |
+
"need assignment variable for loops")
|
| 260 |
+
if term.has(assign_to):
|
| 261 |
+
raise ValueError("FIXME: lhs present in rhs,\
|
| 262 |
+
this is undefined in CodePrinter")
|
| 263 |
+
|
| 264 |
+
lines.extend(openloop)
|
| 265 |
+
lines.extend(openloop_d)
|
| 266 |
+
text = "%s = %s" % (lhs_printed, StrPrinter.doprint(
|
| 267 |
+
self, assign_to + term))
|
| 268 |
+
lines.append(self._get_statement(text))
|
| 269 |
+
lines.extend(closeloop_d)
|
| 270 |
+
lines.extend(closeloop)
|
| 271 |
+
|
| 272 |
+
return "\n".join(lines)
|
| 273 |
+
|
| 274 |
+
def _get_expression_indices(self, expr, assign_to):
|
| 275 |
+
from sympy.tensor import get_indices
|
| 276 |
+
rinds, junk = get_indices(expr)
|
| 277 |
+
linds, junk = get_indices(assign_to)
|
| 278 |
+
|
| 279 |
+
# support broadcast of scalar
|
| 280 |
+
if linds and not rinds:
|
| 281 |
+
rinds = linds
|
| 282 |
+
if rinds != linds:
|
| 283 |
+
raise ValueError("lhs indices must match non-dummy"
|
| 284 |
+
" rhs indices in %s" % expr)
|
| 285 |
+
|
| 286 |
+
return self._sort_optimized(rinds, assign_to)
|
| 287 |
+
|
| 288 |
+
def _sort_optimized(self, indices, expr):
|
| 289 |
+
|
| 290 |
+
from sympy.tensor.indexed import Indexed
|
| 291 |
+
|
| 292 |
+
if not indices:
|
| 293 |
+
return []
|
| 294 |
+
|
| 295 |
+
# determine optimized loop order by giving a score to each index
|
| 296 |
+
# the index with the highest score are put in the innermost loop.
|
| 297 |
+
score_table = {}
|
| 298 |
+
for i in indices:
|
| 299 |
+
score_table[i] = 0
|
| 300 |
+
|
| 301 |
+
arrays = expr.atoms(Indexed)
|
| 302 |
+
for arr in arrays:
|
| 303 |
+
for p, ind in enumerate(arr.indices):
|
| 304 |
+
try:
|
| 305 |
+
score_table[ind] += self._rate_index_position(p)
|
| 306 |
+
except KeyError:
|
| 307 |
+
pass
|
| 308 |
+
|
| 309 |
+
return sorted(indices, key=lambda x: score_table[x])
|
| 310 |
+
|
| 311 |
+
def _rate_index_position(self, p):
|
| 312 |
+
"""function to calculate score based on position among indices
|
| 313 |
+
|
| 314 |
+
This method is used to sort loops in an optimized order, see
|
| 315 |
+
CodePrinter._sort_optimized()
|
| 316 |
+
"""
|
| 317 |
+
raise NotImplementedError("This function must be implemented by "
|
| 318 |
+
"subclass of CodePrinter.")
|
| 319 |
+
|
| 320 |
+
def _get_statement(self, codestring):
|
| 321 |
+
"""Formats a codestring with the proper line ending."""
|
| 322 |
+
raise NotImplementedError("This function must be implemented by "
|
| 323 |
+
"subclass of CodePrinter.")
|
| 324 |
+
|
| 325 |
+
def _get_comment(self, text):
|
| 326 |
+
"""Formats a text string as a comment."""
|
| 327 |
+
raise NotImplementedError("This function must be implemented by "
|
| 328 |
+
"subclass of CodePrinter.")
|
| 329 |
+
|
| 330 |
+
def _declare_number_const(self, name, value):
|
| 331 |
+
"""Declare a numeric constant at the top of a function"""
|
| 332 |
+
raise NotImplementedError("This function must be implemented by "
|
| 333 |
+
"subclass of CodePrinter.")
|
| 334 |
+
|
| 335 |
+
def _format_code(self, lines):
|
| 336 |
+
"""Take in a list of lines of code, and format them accordingly.
|
| 337 |
+
|
| 338 |
+
This may include indenting, wrapping long lines, etc..."""
|
| 339 |
+
raise NotImplementedError("This function must be implemented by "
|
| 340 |
+
"subclass of CodePrinter.")
|
| 341 |
+
|
| 342 |
+
def _get_loop_opening_ending(self, indices):
|
| 343 |
+
"""Returns a tuple (open_lines, close_lines) containing lists
|
| 344 |
+
of codelines"""
|
| 345 |
+
raise NotImplementedError("This function must be implemented by "
|
| 346 |
+
"subclass of CodePrinter.")
|
| 347 |
+
|
| 348 |
+
def _print_Dummy(self, expr):
|
| 349 |
+
if expr.name.startswith('Dummy_'):
|
| 350 |
+
return '_' + expr.name
|
| 351 |
+
else:
|
| 352 |
+
return '%s_%d' % (expr.name, expr.dummy_index)
|
| 353 |
+
|
| 354 |
+
def _print_Idx(self, expr):
|
| 355 |
+
return self._print(expr.label)
|
| 356 |
+
|
| 357 |
+
def _print_CodeBlock(self, expr):
|
| 358 |
+
return '\n'.join([self._print(i) for i in expr.args])
|
| 359 |
+
|
| 360 |
+
def _print_String(self, string):
|
| 361 |
+
return str(string)
|
| 362 |
+
|
| 363 |
+
def _print_QuotedString(self, arg):
|
| 364 |
+
return '"%s"' % arg.text
|
| 365 |
+
|
| 366 |
+
def _print_Comment(self, string):
|
| 367 |
+
return self._get_comment(str(string))
|
| 368 |
+
|
| 369 |
+
def _print_Assignment(self, expr):
|
| 370 |
+
from sympy.codegen.ast import Assignment
|
| 371 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 372 |
+
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
| 373 |
+
from sympy.tensor.indexed import IndexedBase
|
| 374 |
+
lhs = expr.lhs
|
| 375 |
+
rhs = expr.rhs
|
| 376 |
+
# We special case assignments that take multiple lines
|
| 377 |
+
if isinstance(expr.rhs, Piecewise):
|
| 378 |
+
# Here we modify Piecewise so each expression is now
|
| 379 |
+
# an Assignment, and then continue on the print.
|
| 380 |
+
expressions = []
|
| 381 |
+
conditions = []
|
| 382 |
+
for (e, c) in rhs.args:
|
| 383 |
+
expressions.append(Assignment(lhs, e))
|
| 384 |
+
conditions.append(c)
|
| 385 |
+
temp = Piecewise(*zip(expressions, conditions))
|
| 386 |
+
return self._print(temp)
|
| 387 |
+
elif isinstance(lhs, MatrixSymbol):
|
| 388 |
+
# Here we form an Assignment for each element in the array,
|
| 389 |
+
# printing each one.
|
| 390 |
+
lines = []
|
| 391 |
+
for (i, j) in self._traverse_matrix_indices(lhs):
|
| 392 |
+
temp = Assignment(lhs[i, j], rhs[i, j])
|
| 393 |
+
code0 = self._print(temp)
|
| 394 |
+
lines.append(code0)
|
| 395 |
+
return "\n".join(lines)
|
| 396 |
+
elif self._settings.get("contract", False) and (lhs.has(IndexedBase) or
|
| 397 |
+
rhs.has(IndexedBase)):
|
| 398 |
+
# Here we check if there is looping to be done, and if so
|
| 399 |
+
# print the required loops.
|
| 400 |
+
return self._doprint_loops(rhs, lhs)
|
| 401 |
+
else:
|
| 402 |
+
lhs_code = self._print(lhs)
|
| 403 |
+
rhs_code = self._print(rhs)
|
| 404 |
+
return self._get_statement("%s = %s" % (lhs_code, rhs_code))
|
| 405 |
+
|
| 406 |
+
def _print_AugmentedAssignment(self, expr):
|
| 407 |
+
lhs_code = self._print(expr.lhs)
|
| 408 |
+
rhs_code = self._print(expr.rhs)
|
| 409 |
+
return self._get_statement("{} {} {}".format(
|
| 410 |
+
*(self._print(arg) for arg in [lhs_code, expr.op, rhs_code])))
|
| 411 |
+
|
| 412 |
+
def _print_FunctionCall(self, expr):
|
| 413 |
+
return '%s(%s)' % (
|
| 414 |
+
expr.name,
|
| 415 |
+
', '.join((self._print(arg) for arg in expr.function_args)))
|
| 416 |
+
|
| 417 |
+
def _print_Variable(self, expr):
|
| 418 |
+
return self._print(expr.symbol)
|
| 419 |
+
|
| 420 |
+
def _print_Symbol(self, expr):
|
| 421 |
+
name = super()._print_Symbol(expr)
|
| 422 |
+
|
| 423 |
+
if name in self.reserved_words:
|
| 424 |
+
if self._settings['error_on_reserved']:
|
| 425 |
+
msg = ('This expression includes the symbol "{}" which is a '
|
| 426 |
+
'reserved keyword in this language.')
|
| 427 |
+
raise ValueError(msg.format(name))
|
| 428 |
+
return name + self._settings['reserved_word_suffix']
|
| 429 |
+
else:
|
| 430 |
+
return name
|
| 431 |
+
|
| 432 |
+
def _can_print(self, name):
|
| 433 |
+
""" Check if function ``name`` is either a known function or has its own
|
| 434 |
+
printing method. Used to check if rewriting is possible."""
|
| 435 |
+
return name in self.known_functions or getattr(self, '_print_{}'.format(name), False)
|
| 436 |
+
|
| 437 |
+
def _print_Function(self, expr):
|
| 438 |
+
if expr.func.__name__ in self.known_functions:
|
| 439 |
+
cond_func = self.known_functions[expr.func.__name__]
|
| 440 |
+
if isinstance(cond_func, str):
|
| 441 |
+
return "%s(%s)" % (cond_func, self.stringify(expr.args, ", "))
|
| 442 |
+
else:
|
| 443 |
+
for cond, func in cond_func:
|
| 444 |
+
if cond(*expr.args):
|
| 445 |
+
break
|
| 446 |
+
if func is not None:
|
| 447 |
+
try:
|
| 448 |
+
return func(*[self.parenthesize(item, 0) for item in expr.args])
|
| 449 |
+
except TypeError:
|
| 450 |
+
return "%s(%s)" % (func, self.stringify(expr.args, ", "))
|
| 451 |
+
elif hasattr(expr, '_imp_') and isinstance(expr._imp_, Lambda):
|
| 452 |
+
# inlined function
|
| 453 |
+
return self._print(expr._imp_(*expr.args))
|
| 454 |
+
elif expr.func.__name__ in self._rewriteable_functions:
|
| 455 |
+
# Simple rewrite to supported function possible
|
| 456 |
+
target_f, required_fs = self._rewriteable_functions[expr.func.__name__]
|
| 457 |
+
if self._can_print(target_f) and all(self._can_print(f) for f in required_fs):
|
| 458 |
+
return '(' + self._print(expr.rewrite(target_f)) + ')'
|
| 459 |
+
|
| 460 |
+
if expr.is_Function and self._settings.get('allow_unknown_functions', False):
|
| 461 |
+
return '%s(%s)' % (self._print(expr.func), ', '.join(map(self._print, expr.args)))
|
| 462 |
+
else:
|
| 463 |
+
return self._print_not_supported(expr)
|
| 464 |
+
|
| 465 |
+
_print_Expr = _print_Function
|
| 466 |
+
|
| 467 |
+
def _print_Derivative(self, expr):
|
| 468 |
+
obj, *wrt_order_pairs = expr.args
|
| 469 |
+
for func_arg in obj.args:
|
| 470 |
+
if not func_arg.is_Symbol:
|
| 471 |
+
raise ValueError("%s._print_Derivative(...) only supports functions with symbols as arguments." %
|
| 472 |
+
self.__class__.__name__)
|
| 473 |
+
meth_name = '_print_Derivative_%s' % obj.func.__name__
|
| 474 |
+
pmeth = getattr(self, meth_name, None)
|
| 475 |
+
if pmeth is None:
|
| 476 |
+
if self._settings.get('strict', False):
|
| 477 |
+
raise PrintMethodNotImplementedError(
|
| 478 |
+
f"Unsupported by {type(self)}: {type(expr)}" +
|
| 479 |
+
f"\nPrinter has no method: {meth_name}" +
|
| 480 |
+
"\nSet the printer option 'strict' to False in order to generate partially printed code."
|
| 481 |
+
)
|
| 482 |
+
return self._print_not_supported(expr)
|
| 483 |
+
orders = dict(wrt_order_pairs)
|
| 484 |
+
seq_orders = [orders[arg] for arg in obj.args]
|
| 485 |
+
return pmeth(obj.args, seq_orders)
|
| 486 |
+
|
| 487 |
+
# Don't inherit the str-printer method for Heaviside to the code printers
|
| 488 |
+
_print_Heaviside = None
|
| 489 |
+
|
| 490 |
+
def _print_NumberSymbol(self, expr):
|
| 491 |
+
if self._settings.get("inline", False):
|
| 492 |
+
return self._print(Float(expr.evalf(self._settings["precision"])))
|
| 493 |
+
else:
|
| 494 |
+
# A Number symbol that is not implemented here or with _printmethod
|
| 495 |
+
# is registered and evaluated
|
| 496 |
+
self._number_symbols.add((expr,
|
| 497 |
+
Float(expr.evalf(self._settings["precision"]))))
|
| 498 |
+
return str(expr)
|
| 499 |
+
|
| 500 |
+
def _print_Catalan(self, expr):
|
| 501 |
+
return self._print_NumberSymbol(expr)
|
| 502 |
+
def _print_EulerGamma(self, expr):
|
| 503 |
+
return self._print_NumberSymbol(expr)
|
| 504 |
+
def _print_GoldenRatio(self, expr):
|
| 505 |
+
return self._print_NumberSymbol(expr)
|
| 506 |
+
def _print_TribonacciConstant(self, expr):
|
| 507 |
+
return self._print_NumberSymbol(expr)
|
| 508 |
+
def _print_Exp1(self, expr):
|
| 509 |
+
return self._print_NumberSymbol(expr)
|
| 510 |
+
def _print_Pi(self, expr):
|
| 511 |
+
return self._print_NumberSymbol(expr)
|
| 512 |
+
|
| 513 |
+
def _print_And(self, expr):
|
| 514 |
+
PREC = precedence(expr)
|
| 515 |
+
return (" %s " % self._operators['and']).join(self.parenthesize(a, PREC)
|
| 516 |
+
for a in sorted(expr.args, key=default_sort_key))
|
| 517 |
+
|
| 518 |
+
def _print_Or(self, expr):
|
| 519 |
+
PREC = precedence(expr)
|
| 520 |
+
return (" %s " % self._operators['or']).join(self.parenthesize(a, PREC)
|
| 521 |
+
for a in sorted(expr.args, key=default_sort_key))
|
| 522 |
+
|
| 523 |
+
def _print_Xor(self, expr):
|
| 524 |
+
if self._operators.get('xor') is None:
|
| 525 |
+
return self._print(expr.to_nnf())
|
| 526 |
+
PREC = precedence(expr)
|
| 527 |
+
return (" %s " % self._operators['xor']).join(self.parenthesize(a, PREC)
|
| 528 |
+
for a in expr.args)
|
| 529 |
+
|
| 530 |
+
def _print_Equivalent(self, expr):
|
| 531 |
+
if self._operators.get('equivalent') is None:
|
| 532 |
+
return self._print(expr.to_nnf())
|
| 533 |
+
PREC = precedence(expr)
|
| 534 |
+
return (" %s " % self._operators['equivalent']).join(self.parenthesize(a, PREC)
|
| 535 |
+
for a in expr.args)
|
| 536 |
+
|
| 537 |
+
def _print_Not(self, expr):
|
| 538 |
+
PREC = precedence(expr)
|
| 539 |
+
return self._operators['not'] + self.parenthesize(expr.args[0], PREC)
|
| 540 |
+
|
| 541 |
+
def _print_BooleanFunction(self, expr):
|
| 542 |
+
return self._print(expr.to_nnf())
|
| 543 |
+
|
| 544 |
+
def _print_isnan(self, arg):
|
| 545 |
+
return 'isnan(%s)' % self._print(*arg.args)
|
| 546 |
+
|
| 547 |
+
def _print_isinf(self, arg):
|
| 548 |
+
return 'isinf(%s)' % self._print(*arg.args)
|
| 549 |
+
|
| 550 |
+
def _print_Mul(self, expr):
|
| 551 |
+
|
| 552 |
+
prec = precedence(expr)
|
| 553 |
+
|
| 554 |
+
c, e = expr.as_coeff_Mul()
|
| 555 |
+
if c < 0:
|
| 556 |
+
expr = _keep_coeff(-c, e)
|
| 557 |
+
sign = "-"
|
| 558 |
+
else:
|
| 559 |
+
sign = ""
|
| 560 |
+
|
| 561 |
+
a = [] # items in the numerator
|
| 562 |
+
b = [] # items that are in the denominator (if any)
|
| 563 |
+
|
| 564 |
+
pow_paren = [] # Will collect all pow with more than one base element and exp = -1
|
| 565 |
+
|
| 566 |
+
if self.order not in ('old', 'none'):
|
| 567 |
+
args = expr.as_ordered_factors()
|
| 568 |
+
else:
|
| 569 |
+
# use make_args in case expr was something like -x -> x
|
| 570 |
+
args = Mul.make_args(expr)
|
| 571 |
+
|
| 572 |
+
# Gather args for numerator/denominator
|
| 573 |
+
for item in args:
|
| 574 |
+
if item.is_commutative and item.is_Pow and item.exp.is_Rational and item.exp.is_negative:
|
| 575 |
+
if item.exp != -1:
|
| 576 |
+
b.append(Pow(item.base, -item.exp, evaluate=False))
|
| 577 |
+
else:
|
| 578 |
+
if len(item.args[0].args) != 1 and isinstance(item.base, Mul): # To avoid situations like #14160
|
| 579 |
+
pow_paren.append(item)
|
| 580 |
+
b.append(Pow(item.base, -item.exp))
|
| 581 |
+
else:
|
| 582 |
+
a.append(item)
|
| 583 |
+
|
| 584 |
+
a = a or [S.One]
|
| 585 |
+
|
| 586 |
+
if len(a) == 1 and sign == "-":
|
| 587 |
+
# Unary minus does not have a SymPy class, and hence there's no
|
| 588 |
+
# precedence weight associated with it, Python's unary minus has
|
| 589 |
+
# an operator precedence between multiplication and exponentiation,
|
| 590 |
+
# so we use this to compute a weight.
|
| 591 |
+
a_str = [self.parenthesize(a[0], 0.5*(PRECEDENCE["Pow"]+PRECEDENCE["Mul"]))]
|
| 592 |
+
else:
|
| 593 |
+
a_str = [self.parenthesize(x, prec) for x in a]
|
| 594 |
+
b_str = [self.parenthesize(x, prec) for x in b]
|
| 595 |
+
|
| 596 |
+
# To parenthesize Pow with exp = -1 and having more than one Symbol
|
| 597 |
+
for item in pow_paren:
|
| 598 |
+
if item.base in b:
|
| 599 |
+
b_str[b.index(item.base)] = "(%s)" % b_str[b.index(item.base)]
|
| 600 |
+
|
| 601 |
+
if not b:
|
| 602 |
+
return sign + '*'.join(a_str)
|
| 603 |
+
elif len(b) == 1:
|
| 604 |
+
return sign + '*'.join(a_str) + "/" + b_str[0]
|
| 605 |
+
else:
|
| 606 |
+
return sign + '*'.join(a_str) + "/(%s)" % '*'.join(b_str)
|
| 607 |
+
|
| 608 |
+
def _print_not_supported(self, expr):
|
| 609 |
+
if self._settings.get('strict', False):
|
| 610 |
+
raise PrintMethodNotImplementedError(
|
| 611 |
+
f"Unsupported by {type(self)}: {type(expr)}" +
|
| 612 |
+
"\nSet the printer option 'strict' to False in order to generate partially printed code."
|
| 613 |
+
)
|
| 614 |
+
try:
|
| 615 |
+
self._not_supported.add(expr)
|
| 616 |
+
except TypeError:
|
| 617 |
+
# not hashable
|
| 618 |
+
pass
|
| 619 |
+
return self.emptyPrinter(expr)
|
| 620 |
+
|
| 621 |
+
# The following can not be simply translated into C or Fortran
|
| 622 |
+
_print_Basic = _print_not_supported
|
| 623 |
+
_print_ComplexInfinity = _print_not_supported
|
| 624 |
+
_print_ExprCondPair = _print_not_supported
|
| 625 |
+
_print_GeometryEntity = _print_not_supported
|
| 626 |
+
_print_Infinity = _print_not_supported
|
| 627 |
+
_print_Integral = _print_not_supported
|
| 628 |
+
_print_Interval = _print_not_supported
|
| 629 |
+
_print_AccumulationBounds = _print_not_supported
|
| 630 |
+
_print_Limit = _print_not_supported
|
| 631 |
+
_print_MatrixBase = _print_not_supported
|
| 632 |
+
_print_DeferredVector = _print_not_supported
|
| 633 |
+
_print_NaN = _print_not_supported
|
| 634 |
+
_print_NegativeInfinity = _print_not_supported
|
| 635 |
+
_print_Order = _print_not_supported
|
| 636 |
+
_print_RootOf = _print_not_supported
|
| 637 |
+
_print_RootsOf = _print_not_supported
|
| 638 |
+
_print_RootSum = _print_not_supported
|
| 639 |
+
_print_Uniform = _print_not_supported
|
| 640 |
+
_print_Unit = _print_not_supported
|
| 641 |
+
_print_Wild = _print_not_supported
|
| 642 |
+
_print_WildFunction = _print_not_supported
|
| 643 |
+
_print_Relational = _print_not_supported
|
| 644 |
+
|
| 645 |
+
|
| 646 |
+
# Code printer functions. These are included in this file so that they can be
|
| 647 |
+
# imported in the top-level __init__.py without importing the sympy.codegen
|
| 648 |
+
# module.
|
| 649 |
+
|
| 650 |
+
def ccode(expr, assign_to=None, standard='c99', **settings):
|
| 651 |
+
"""Converts an expr to a string of c code
|
| 652 |
+
|
| 653 |
+
Parameters
|
| 654 |
+
==========
|
| 655 |
+
|
| 656 |
+
expr : Expr
|
| 657 |
+
A SymPy expression to be converted.
|
| 658 |
+
assign_to : optional
|
| 659 |
+
When given, the argument is used as the name of the variable to which
|
| 660 |
+
the expression is assigned. Can be a string, ``Symbol``,
|
| 661 |
+
``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of
|
| 662 |
+
line-wrapping, or for expressions that generate multi-line statements.
|
| 663 |
+
standard : str, optional
|
| 664 |
+
String specifying the standard. If your compiler supports a more modern
|
| 665 |
+
standard you may set this to 'c99' to allow the printer to use more math
|
| 666 |
+
functions. [default='c89'].
|
| 667 |
+
precision : integer, optional
|
| 668 |
+
The precision for numbers such as pi [default=17].
|
| 669 |
+
user_functions : dict, optional
|
| 670 |
+
A dictionary where the keys are string representations of either
|
| 671 |
+
``FunctionClass`` or ``UndefinedFunction`` instances and the values
|
| 672 |
+
are their desired C string representations. Alternatively, the
|
| 673 |
+
dictionary value can be a list of tuples i.e. [(argument_test,
|
| 674 |
+
cfunction_string)] or [(argument_test, cfunction_formater)]. See below
|
| 675 |
+
for examples.
|
| 676 |
+
dereference : iterable, optional
|
| 677 |
+
An iterable of symbols that should be dereferenced in the printed code
|
| 678 |
+
expression. These would be values passed by address to the function.
|
| 679 |
+
For example, if ``dereference=[a]``, the resulting code would print
|
| 680 |
+
``(*a)`` instead of ``a``.
|
| 681 |
+
human : bool, optional
|
| 682 |
+
If True, the result is a single string that may contain some constant
|
| 683 |
+
declarations for the number symbols. If False, the same information is
|
| 684 |
+
returned in a tuple of (symbols_to_declare, not_supported_functions,
|
| 685 |
+
code_text). [default=True].
|
| 686 |
+
contract: bool, optional
|
| 687 |
+
If True, ``Indexed`` instances are assumed to obey tensor contraction
|
| 688 |
+
rules and the corresponding nested loops over indices are generated.
|
| 689 |
+
Setting contract=False will not generate loops, instead the user is
|
| 690 |
+
responsible to provide values for the indices in the code.
|
| 691 |
+
[default=True].
|
| 692 |
+
|
| 693 |
+
Examples
|
| 694 |
+
========
|
| 695 |
+
|
| 696 |
+
>>> from sympy import ccode, symbols, Rational, sin, ceiling, Abs, Function
|
| 697 |
+
>>> x, tau = symbols("x, tau")
|
| 698 |
+
>>> expr = (2*tau)**Rational(7, 2)
|
| 699 |
+
>>> ccode(expr)
|
| 700 |
+
'8*M_SQRT2*pow(tau, 7.0/2.0)'
|
| 701 |
+
>>> ccode(expr, math_macros={})
|
| 702 |
+
'8*sqrt(2)*pow(tau, 7.0/2.0)'
|
| 703 |
+
>>> ccode(sin(x), assign_to="s")
|
| 704 |
+
's = sin(x);'
|
| 705 |
+
>>> from sympy.codegen.ast import real, float80
|
| 706 |
+
>>> ccode(expr, type_aliases={real: float80})
|
| 707 |
+
'8*M_SQRT2l*powl(tau, 7.0L/2.0L)'
|
| 708 |
+
|
| 709 |
+
Simple custom printing can be defined for certain types by passing a
|
| 710 |
+
dictionary of {"type" : "function"} to the ``user_functions`` kwarg.
|
| 711 |
+
Alternatively, the dictionary value can be a list of tuples i.e.
|
| 712 |
+
[(argument_test, cfunction_string)].
|
| 713 |
+
|
| 714 |
+
>>> custom_functions = {
|
| 715 |
+
... "ceiling": "CEIL",
|
| 716 |
+
... "Abs": [(lambda x: not x.is_integer, "fabs"),
|
| 717 |
+
... (lambda x: x.is_integer, "ABS")],
|
| 718 |
+
... "func": "f"
|
| 719 |
+
... }
|
| 720 |
+
>>> func = Function('func')
|
| 721 |
+
>>> ccode(func(Abs(x) + ceiling(x)), standard='C89', user_functions=custom_functions)
|
| 722 |
+
'f(fabs(x) + CEIL(x))'
|
| 723 |
+
|
| 724 |
+
or if the C-function takes a subset of the original arguments:
|
| 725 |
+
|
| 726 |
+
>>> ccode(2**x + 3**x, standard='C99', user_functions={'Pow': [
|
| 727 |
+
... (lambda b, e: b == 2, lambda b, e: 'exp2(%s)' % e),
|
| 728 |
+
... (lambda b, e: b != 2, 'pow')]})
|
| 729 |
+
'exp2(x) + pow(3, x)'
|
| 730 |
+
|
| 731 |
+
``Piecewise`` expressions are converted into conditionals. If an
|
| 732 |
+
``assign_to`` variable is provided an if statement is created, otherwise
|
| 733 |
+
the ternary operator is used. Note that if the ``Piecewise`` lacks a
|
| 734 |
+
default term, represented by ``(expr, True)`` then an error will be thrown.
|
| 735 |
+
This is to prevent generating an expression that may not evaluate to
|
| 736 |
+
anything.
|
| 737 |
+
|
| 738 |
+
>>> from sympy import Piecewise
|
| 739 |
+
>>> expr = Piecewise((x + 1, x > 0), (x, True))
|
| 740 |
+
>>> print(ccode(expr, tau, standard='C89'))
|
| 741 |
+
if (x > 0) {
|
| 742 |
+
tau = x + 1;
|
| 743 |
+
}
|
| 744 |
+
else {
|
| 745 |
+
tau = x;
|
| 746 |
+
}
|
| 747 |
+
|
| 748 |
+
Support for loops is provided through ``Indexed`` types. With
|
| 749 |
+
``contract=True`` these expressions will be turned into loops, whereas
|
| 750 |
+
``contract=False`` will just print the assignment expression that should be
|
| 751 |
+
looped over:
|
| 752 |
+
|
| 753 |
+
>>> from sympy import Eq, IndexedBase, Idx
|
| 754 |
+
>>> len_y = 5
|
| 755 |
+
>>> y = IndexedBase('y', shape=(len_y,))
|
| 756 |
+
>>> t = IndexedBase('t', shape=(len_y,))
|
| 757 |
+
>>> Dy = IndexedBase('Dy', shape=(len_y-1,))
|
| 758 |
+
>>> i = Idx('i', len_y-1)
|
| 759 |
+
>>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i]))
|
| 760 |
+
>>> ccode(e.rhs, assign_to=e.lhs, contract=False, standard='C89')
|
| 761 |
+
'Dy[i] = (y[i + 1] - y[i])/(t[i + 1] - t[i]);'
|
| 762 |
+
|
| 763 |
+
Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions
|
| 764 |
+
must be provided to ``assign_to``. Note that any expression that can be
|
| 765 |
+
generated normally can also exist inside a Matrix:
|
| 766 |
+
|
| 767 |
+
>>> from sympy import Matrix, MatrixSymbol
|
| 768 |
+
>>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)])
|
| 769 |
+
>>> A = MatrixSymbol('A', 3, 1)
|
| 770 |
+
>>> print(ccode(mat, A, standard='C89'))
|
| 771 |
+
A[0] = pow(x, 2);
|
| 772 |
+
if (x > 0) {
|
| 773 |
+
A[1] = x + 1;
|
| 774 |
+
}
|
| 775 |
+
else {
|
| 776 |
+
A[1] = x;
|
| 777 |
+
}
|
| 778 |
+
A[2] = sin(x);
|
| 779 |
+
"""
|
| 780 |
+
from sympy.printing.c import c_code_printers
|
| 781 |
+
return c_code_printers[standard.lower()](settings).doprint(expr, assign_to)
|
| 782 |
+
|
| 783 |
+
def print_ccode(expr, **settings):
|
| 784 |
+
"""Prints C representation of the given expression."""
|
| 785 |
+
print(ccode(expr, **settings))
|
| 786 |
+
|
| 787 |
+
def fcode(expr, assign_to=None, **settings):
|
| 788 |
+
"""Converts an expr to a string of fortran code
|
| 789 |
+
|
| 790 |
+
Parameters
|
| 791 |
+
==========
|
| 792 |
+
|
| 793 |
+
expr : Expr
|
| 794 |
+
A SymPy expression to be converted.
|
| 795 |
+
assign_to : optional
|
| 796 |
+
When given, the argument is used as the name of the variable to which
|
| 797 |
+
the expression is assigned. Can be a string, ``Symbol``,
|
| 798 |
+
``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of
|
| 799 |
+
line-wrapping, or for expressions that generate multi-line statements.
|
| 800 |
+
precision : integer, optional
|
| 801 |
+
DEPRECATED. Use type_mappings instead. The precision for numbers such
|
| 802 |
+
as pi [default=17].
|
| 803 |
+
user_functions : dict, optional
|
| 804 |
+
A dictionary where keys are ``FunctionClass`` instances and values are
|
| 805 |
+
their string representations. Alternatively, the dictionary value can
|
| 806 |
+
be a list of tuples i.e. [(argument_test, cfunction_string)]. See below
|
| 807 |
+
for examples.
|
| 808 |
+
human : bool, optional
|
| 809 |
+
If True, the result is a single string that may contain some constant
|
| 810 |
+
declarations for the number symbols. If False, the same information is
|
| 811 |
+
returned in a tuple of (symbols_to_declare, not_supported_functions,
|
| 812 |
+
code_text). [default=True].
|
| 813 |
+
contract: bool, optional
|
| 814 |
+
If True, ``Indexed`` instances are assumed to obey tensor contraction
|
| 815 |
+
rules and the corresponding nested loops over indices are generated.
|
| 816 |
+
Setting contract=False will not generate loops, instead the user is
|
| 817 |
+
responsible to provide values for the indices in the code.
|
| 818 |
+
[default=True].
|
| 819 |
+
source_format : optional
|
| 820 |
+
The source format can be either 'fixed' or 'free'. [default='fixed']
|
| 821 |
+
standard : integer, optional
|
| 822 |
+
The Fortran standard to be followed. This is specified as an integer.
|
| 823 |
+
Acceptable standards are 66, 77, 90, 95, 2003, and 2008. Default is 77.
|
| 824 |
+
Note that currently the only distinction internally is between
|
| 825 |
+
standards before 95, and those 95 and after. This may change later as
|
| 826 |
+
more features are added.
|
| 827 |
+
name_mangling : bool, optional
|
| 828 |
+
If True, then the variables that would become identical in
|
| 829 |
+
case-insensitive Fortran are mangled by appending different number
|
| 830 |
+
of ``_`` at the end. If False, SymPy Will not interfere with naming of
|
| 831 |
+
variables. [default=True]
|
| 832 |
+
|
| 833 |
+
Examples
|
| 834 |
+
========
|
| 835 |
+
|
| 836 |
+
>>> from sympy import fcode, symbols, Rational, sin, ceiling, floor
|
| 837 |
+
>>> x, tau = symbols("x, tau")
|
| 838 |
+
>>> fcode((2*tau)**Rational(7, 2))
|
| 839 |
+
' 8*sqrt(2.0d0)*tau**(7.0d0/2.0d0)'
|
| 840 |
+
>>> fcode(sin(x), assign_to="s")
|
| 841 |
+
' s = sin(x)'
|
| 842 |
+
|
| 843 |
+
Custom printing can be defined for certain types by passing a dictionary of
|
| 844 |
+
"type" : "function" to the ``user_functions`` kwarg. Alternatively, the
|
| 845 |
+
dictionary value can be a list of tuples i.e. [(argument_test,
|
| 846 |
+
cfunction_string)].
|
| 847 |
+
|
| 848 |
+
>>> custom_functions = {
|
| 849 |
+
... "ceiling": "CEIL",
|
| 850 |
+
... "floor": [(lambda x: not x.is_integer, "FLOOR1"),
|
| 851 |
+
... (lambda x: x.is_integer, "FLOOR2")]
|
| 852 |
+
... }
|
| 853 |
+
>>> fcode(floor(x) + ceiling(x), user_functions=custom_functions)
|
| 854 |
+
' CEIL(x) + FLOOR1(x)'
|
| 855 |
+
|
| 856 |
+
``Piecewise`` expressions are converted into conditionals. If an
|
| 857 |
+
``assign_to`` variable is provided an if statement is created, otherwise
|
| 858 |
+
the ternary operator is used. Note that if the ``Piecewise`` lacks a
|
| 859 |
+
default term, represented by ``(expr, True)`` then an error will be thrown.
|
| 860 |
+
This is to prevent generating an expression that may not evaluate to
|
| 861 |
+
anything.
|
| 862 |
+
|
| 863 |
+
>>> from sympy import Piecewise
|
| 864 |
+
>>> expr = Piecewise((x + 1, x > 0), (x, True))
|
| 865 |
+
>>> print(fcode(expr, tau))
|
| 866 |
+
if (x > 0) then
|
| 867 |
+
tau = x + 1
|
| 868 |
+
else
|
| 869 |
+
tau = x
|
| 870 |
+
end if
|
| 871 |
+
|
| 872 |
+
Support for loops is provided through ``Indexed`` types. With
|
| 873 |
+
``contract=True`` these expressions will be turned into loops, whereas
|
| 874 |
+
``contract=False`` will just print the assignment expression that should be
|
| 875 |
+
looped over:
|
| 876 |
+
|
| 877 |
+
>>> from sympy import Eq, IndexedBase, Idx
|
| 878 |
+
>>> len_y = 5
|
| 879 |
+
>>> y = IndexedBase('y', shape=(len_y,))
|
| 880 |
+
>>> t = IndexedBase('t', shape=(len_y,))
|
| 881 |
+
>>> Dy = IndexedBase('Dy', shape=(len_y-1,))
|
| 882 |
+
>>> i = Idx('i', len_y-1)
|
| 883 |
+
>>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i]))
|
| 884 |
+
>>> fcode(e.rhs, assign_to=e.lhs, contract=False)
|
| 885 |
+
' Dy(i) = (y(i + 1) - y(i))/(t(i + 1) - t(i))'
|
| 886 |
+
|
| 887 |
+
Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions
|
| 888 |
+
must be provided to ``assign_to``. Note that any expression that can be
|
| 889 |
+
generated normally can also exist inside a Matrix:
|
| 890 |
+
|
| 891 |
+
>>> from sympy import Matrix, MatrixSymbol
|
| 892 |
+
>>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)])
|
| 893 |
+
>>> A = MatrixSymbol('A', 3, 1)
|
| 894 |
+
>>> print(fcode(mat, A))
|
| 895 |
+
A(1, 1) = x**2
|
| 896 |
+
if (x > 0) then
|
| 897 |
+
A(2, 1) = x + 1
|
| 898 |
+
else
|
| 899 |
+
A(2, 1) = x
|
| 900 |
+
end if
|
| 901 |
+
A(3, 1) = sin(x)
|
| 902 |
+
"""
|
| 903 |
+
from sympy.printing.fortran import FCodePrinter
|
| 904 |
+
return FCodePrinter(settings).doprint(expr, assign_to)
|
| 905 |
+
|
| 906 |
+
|
| 907 |
+
def print_fcode(expr, **settings):
|
| 908 |
+
"""Prints the Fortran representation of the given expression.
|
| 909 |
+
|
| 910 |
+
See fcode for the meaning of the optional arguments.
|
| 911 |
+
"""
|
| 912 |
+
print(fcode(expr, **settings))
|
| 913 |
+
|
| 914 |
+
def cxxcode(expr, assign_to=None, standard='c++11', **settings):
|
| 915 |
+
""" C++ equivalent of :func:`~.ccode`. """
|
| 916 |
+
from sympy.printing.cxx import cxx_code_printers
|
| 917 |
+
return cxx_code_printers[standard.lower()](settings).doprint(expr, assign_to)
|
| 918 |
+
|
| 919 |
+
|
| 920 |
+
def rust_code(expr, assign_to=None, **settings):
|
| 921 |
+
"""Converts an expr to a string of Rust code
|
| 922 |
+
|
| 923 |
+
Parameters
|
| 924 |
+
==========
|
| 925 |
+
|
| 926 |
+
expr : Expr
|
| 927 |
+
A SymPy expression to be converted.
|
| 928 |
+
assign_to : optional
|
| 929 |
+
When given, the argument is used as the name of the variable to which
|
| 930 |
+
the expression is assigned. Can be a string, ``Symbol``,
|
| 931 |
+
``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of
|
| 932 |
+
line-wrapping, or for expressions that generate multi-line statements.
|
| 933 |
+
precision : integer, optional
|
| 934 |
+
The precision for numbers such as pi [default=15].
|
| 935 |
+
user_functions : dict, optional
|
| 936 |
+
A dictionary where the keys are string representations of either
|
| 937 |
+
``FunctionClass`` or ``UndefinedFunction`` instances and the values
|
| 938 |
+
are their desired C string representations. Alternatively, the
|
| 939 |
+
dictionary value can be a list of tuples i.e. [(argument_test,
|
| 940 |
+
cfunction_string)]. See below for examples.
|
| 941 |
+
dereference : iterable, optional
|
| 942 |
+
An iterable of symbols that should be dereferenced in the printed code
|
| 943 |
+
expression. These would be values passed by address to the function.
|
| 944 |
+
For example, if ``dereference=[a]``, the resulting code would print
|
| 945 |
+
``(*a)`` instead of ``a``.
|
| 946 |
+
human : bool, optional
|
| 947 |
+
If True, the result is a single string that may contain some constant
|
| 948 |
+
declarations for the number symbols. If False, the same information is
|
| 949 |
+
returned in a tuple of (symbols_to_declare, not_supported_functions,
|
| 950 |
+
code_text). [default=True].
|
| 951 |
+
contract: bool, optional
|
| 952 |
+
If True, ``Indexed`` instances are assumed to obey tensor contraction
|
| 953 |
+
rules and the corresponding nested loops over indices are generated.
|
| 954 |
+
Setting contract=False will not generate loops, instead the user is
|
| 955 |
+
responsible to provide values for the indices in the code.
|
| 956 |
+
[default=True].
|
| 957 |
+
|
| 958 |
+
Examples
|
| 959 |
+
========
|
| 960 |
+
|
| 961 |
+
>>> from sympy import rust_code, symbols, Rational, sin, ceiling, Abs, Function
|
| 962 |
+
>>> x, tau = symbols("x, tau")
|
| 963 |
+
>>> rust_code((2*tau)**Rational(7, 2))
|
| 964 |
+
'8.0*1.4142135623731*tau.powf(7_f64/2.0)'
|
| 965 |
+
>>> rust_code(sin(x), assign_to="s")
|
| 966 |
+
's = x.sin();'
|
| 967 |
+
|
| 968 |
+
Simple custom printing can be defined for certain types by passing a
|
| 969 |
+
dictionary of {"type" : "function"} to the ``user_functions`` kwarg.
|
| 970 |
+
Alternatively, the dictionary value can be a list of tuples i.e.
|
| 971 |
+
[(argument_test, cfunction_string)].
|
| 972 |
+
|
| 973 |
+
>>> custom_functions = {
|
| 974 |
+
... "ceiling": "CEIL",
|
| 975 |
+
... "Abs": [(lambda x: not x.is_integer, "fabs", 4),
|
| 976 |
+
... (lambda x: x.is_integer, "ABS", 4)],
|
| 977 |
+
... "func": "f"
|
| 978 |
+
... }
|
| 979 |
+
>>> func = Function('func')
|
| 980 |
+
>>> rust_code(func(Abs(x) + ceiling(x)), user_functions=custom_functions)
|
| 981 |
+
'(fabs(x) + x.ceil()).f()'
|
| 982 |
+
|
| 983 |
+
``Piecewise`` expressions are converted into conditionals. If an
|
| 984 |
+
``assign_to`` variable is provided an if statement is created, otherwise
|
| 985 |
+
the ternary operator is used. Note that if the ``Piecewise`` lacks a
|
| 986 |
+
default term, represented by ``(expr, True)`` then an error will be thrown.
|
| 987 |
+
This is to prevent generating an expression that may not evaluate to
|
| 988 |
+
anything.
|
| 989 |
+
|
| 990 |
+
>>> from sympy import Piecewise
|
| 991 |
+
>>> expr = Piecewise((x + 1, x > 0), (x, True))
|
| 992 |
+
>>> print(rust_code(expr, tau))
|
| 993 |
+
tau = if (x > 0.0) {
|
| 994 |
+
x + 1
|
| 995 |
+
} else {
|
| 996 |
+
x
|
| 997 |
+
};
|
| 998 |
+
|
| 999 |
+
Support for loops is provided through ``Indexed`` types. With
|
| 1000 |
+
``contract=True`` these expressions will be turned into loops, whereas
|
| 1001 |
+
``contract=False`` will just print the assignment expression that should be
|
| 1002 |
+
looped over:
|
| 1003 |
+
|
| 1004 |
+
>>> from sympy import Eq, IndexedBase, Idx
|
| 1005 |
+
>>> len_y = 5
|
| 1006 |
+
>>> y = IndexedBase('y', shape=(len_y,))
|
| 1007 |
+
>>> t = IndexedBase('t', shape=(len_y,))
|
| 1008 |
+
>>> Dy = IndexedBase('Dy', shape=(len_y-1,))
|
| 1009 |
+
>>> i = Idx('i', len_y-1)
|
| 1010 |
+
>>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i]))
|
| 1011 |
+
>>> rust_code(e.rhs, assign_to=e.lhs, contract=False)
|
| 1012 |
+
'Dy[i] = (y[i + 1] - y[i])/(t[i + 1] - t[i]);'
|
| 1013 |
+
|
| 1014 |
+
Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions
|
| 1015 |
+
must be provided to ``assign_to``. Note that any expression that can be
|
| 1016 |
+
generated normally can also exist inside a Matrix:
|
| 1017 |
+
|
| 1018 |
+
>>> from sympy import Matrix, MatrixSymbol
|
| 1019 |
+
>>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)])
|
| 1020 |
+
>>> A = MatrixSymbol('A', 3, 1)
|
| 1021 |
+
>>> print(rust_code(mat, A))
|
| 1022 |
+
A = [x.powi(2), if (x > 0.0) {
|
| 1023 |
+
x + 1
|
| 1024 |
+
} else {
|
| 1025 |
+
x
|
| 1026 |
+
}, x.sin()];
|
| 1027 |
+
"""
|
| 1028 |
+
from sympy.printing.rust import RustCodePrinter
|
| 1029 |
+
printer = RustCodePrinter(settings)
|
| 1030 |
+
expr = printer._rewrite_known_functions(expr)
|
| 1031 |
+
if isinstance(expr, Expr):
|
| 1032 |
+
for src_func, dst_func in printer.function_overrides.values():
|
| 1033 |
+
expr = expr.replace(src_func, dst_func)
|
| 1034 |
+
return printer.doprint(expr, assign_to)
|
| 1035 |
+
|
| 1036 |
+
|
| 1037 |
+
def print_rust_code(expr, **settings):
|
| 1038 |
+
"""Prints Rust representation of the given expression."""
|
| 1039 |
+
print(rust_code(expr, **settings))
|
.venv/lib/python3.13/site-packages/sympy/printing/mathematica.py
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Mathematica code printer
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
from sympy.core import Basic, Expr, Float
|
| 9 |
+
from sympy.core.sorting import default_sort_key
|
| 10 |
+
|
| 11 |
+
from sympy.printing.codeprinter import CodePrinter
|
| 12 |
+
from sympy.printing.precedence import precedence
|
| 13 |
+
|
| 14 |
+
# Used in MCodePrinter._print_Function(self)
|
| 15 |
+
known_functions = {
|
| 16 |
+
"exp": [(lambda x: True, "Exp")],
|
| 17 |
+
"log": [(lambda x: True, "Log")],
|
| 18 |
+
"sin": [(lambda x: True, "Sin")],
|
| 19 |
+
"cos": [(lambda x: True, "Cos")],
|
| 20 |
+
"tan": [(lambda x: True, "Tan")],
|
| 21 |
+
"cot": [(lambda x: True, "Cot")],
|
| 22 |
+
"sec": [(lambda x: True, "Sec")],
|
| 23 |
+
"csc": [(lambda x: True, "Csc")],
|
| 24 |
+
"asin": [(lambda x: True, "ArcSin")],
|
| 25 |
+
"acos": [(lambda x: True, "ArcCos")],
|
| 26 |
+
"atan": [(lambda x: True, "ArcTan")],
|
| 27 |
+
"acot": [(lambda x: True, "ArcCot")],
|
| 28 |
+
"asec": [(lambda x: True, "ArcSec")],
|
| 29 |
+
"acsc": [(lambda x: True, "ArcCsc")],
|
| 30 |
+
"sinh": [(lambda x: True, "Sinh")],
|
| 31 |
+
"cosh": [(lambda x: True, "Cosh")],
|
| 32 |
+
"tanh": [(lambda x: True, "Tanh")],
|
| 33 |
+
"coth": [(lambda x: True, "Coth")],
|
| 34 |
+
"sech": [(lambda x: True, "Sech")],
|
| 35 |
+
"csch": [(lambda x: True, "Csch")],
|
| 36 |
+
"asinh": [(lambda x: True, "ArcSinh")],
|
| 37 |
+
"acosh": [(lambda x: True, "ArcCosh")],
|
| 38 |
+
"atanh": [(lambda x: True, "ArcTanh")],
|
| 39 |
+
"acoth": [(lambda x: True, "ArcCoth")],
|
| 40 |
+
"asech": [(lambda x: True, "ArcSech")],
|
| 41 |
+
"acsch": [(lambda x: True, "ArcCsch")],
|
| 42 |
+
"sinc": [(lambda x: True, "Sinc")],
|
| 43 |
+
"conjugate": [(lambda x: True, "Conjugate")],
|
| 44 |
+
"Max": [(lambda *x: True, "Max")],
|
| 45 |
+
"Min": [(lambda *x: True, "Min")],
|
| 46 |
+
"erf": [(lambda x: True, "Erf")],
|
| 47 |
+
"erf2": [(lambda *x: True, "Erf")],
|
| 48 |
+
"erfc": [(lambda x: True, "Erfc")],
|
| 49 |
+
"erfi": [(lambda x: True, "Erfi")],
|
| 50 |
+
"erfinv": [(lambda x: True, "InverseErf")],
|
| 51 |
+
"erfcinv": [(lambda x: True, "InverseErfc")],
|
| 52 |
+
"erf2inv": [(lambda *x: True, "InverseErf")],
|
| 53 |
+
"expint": [(lambda *x: True, "ExpIntegralE")],
|
| 54 |
+
"Ei": [(lambda x: True, "ExpIntegralEi")],
|
| 55 |
+
"fresnelc": [(lambda x: True, "FresnelC")],
|
| 56 |
+
"fresnels": [(lambda x: True, "FresnelS")],
|
| 57 |
+
"gamma": [(lambda x: True, "Gamma")],
|
| 58 |
+
"uppergamma": [(lambda *x: True, "Gamma")],
|
| 59 |
+
"polygamma": [(lambda *x: True, "PolyGamma")],
|
| 60 |
+
"loggamma": [(lambda x: True, "LogGamma")],
|
| 61 |
+
"beta": [(lambda *x: True, "Beta")],
|
| 62 |
+
"Ci": [(lambda x: True, "CosIntegral")],
|
| 63 |
+
"Si": [(lambda x: True, "SinIntegral")],
|
| 64 |
+
"Chi": [(lambda x: True, "CoshIntegral")],
|
| 65 |
+
"Shi": [(lambda x: True, "SinhIntegral")],
|
| 66 |
+
"li": [(lambda x: True, "LogIntegral")],
|
| 67 |
+
"factorial": [(lambda x: True, "Factorial")],
|
| 68 |
+
"factorial2": [(lambda x: True, "Factorial2")],
|
| 69 |
+
"subfactorial": [(lambda x: True, "Subfactorial")],
|
| 70 |
+
"catalan": [(lambda x: True, "CatalanNumber")],
|
| 71 |
+
"harmonic": [(lambda *x: True, "HarmonicNumber")],
|
| 72 |
+
"lucas": [(lambda x: True, "LucasL")],
|
| 73 |
+
"RisingFactorial": [(lambda *x: True, "Pochhammer")],
|
| 74 |
+
"FallingFactorial": [(lambda *x: True, "FactorialPower")],
|
| 75 |
+
"laguerre": [(lambda *x: True, "LaguerreL")],
|
| 76 |
+
"assoc_laguerre": [(lambda *x: True, "LaguerreL")],
|
| 77 |
+
"hermite": [(lambda *x: True, "HermiteH")],
|
| 78 |
+
"jacobi": [(lambda *x: True, "JacobiP")],
|
| 79 |
+
"gegenbauer": [(lambda *x: True, "GegenbauerC")],
|
| 80 |
+
"chebyshevt": [(lambda *x: True, "ChebyshevT")],
|
| 81 |
+
"chebyshevu": [(lambda *x: True, "ChebyshevU")],
|
| 82 |
+
"legendre": [(lambda *x: True, "LegendreP")],
|
| 83 |
+
"assoc_legendre": [(lambda *x: True, "LegendreP")],
|
| 84 |
+
"mathieuc": [(lambda *x: True, "MathieuC")],
|
| 85 |
+
"mathieus": [(lambda *x: True, "MathieuS")],
|
| 86 |
+
"mathieucprime": [(lambda *x: True, "MathieuCPrime")],
|
| 87 |
+
"mathieusprime": [(lambda *x: True, "MathieuSPrime")],
|
| 88 |
+
"stieltjes": [(lambda x: True, "StieltjesGamma")],
|
| 89 |
+
"elliptic_e": [(lambda *x: True, "EllipticE")],
|
| 90 |
+
"elliptic_f": [(lambda *x: True, "EllipticE")],
|
| 91 |
+
"elliptic_k": [(lambda x: True, "EllipticK")],
|
| 92 |
+
"elliptic_pi": [(lambda *x: True, "EllipticPi")],
|
| 93 |
+
"zeta": [(lambda *x: True, "Zeta")],
|
| 94 |
+
"dirichlet_eta": [(lambda x: True, "DirichletEta")],
|
| 95 |
+
"riemann_xi": [(lambda x: True, "RiemannXi")],
|
| 96 |
+
"besseli": [(lambda *x: True, "BesselI")],
|
| 97 |
+
"besselj": [(lambda *x: True, "BesselJ")],
|
| 98 |
+
"besselk": [(lambda *x: True, "BesselK")],
|
| 99 |
+
"bessely": [(lambda *x: True, "BesselY")],
|
| 100 |
+
"hankel1": [(lambda *x: True, "HankelH1")],
|
| 101 |
+
"hankel2": [(lambda *x: True, "HankelH2")],
|
| 102 |
+
"airyai": [(lambda x: True, "AiryAi")],
|
| 103 |
+
"airybi": [(lambda x: True, "AiryBi")],
|
| 104 |
+
"airyaiprime": [(lambda x: True, "AiryAiPrime")],
|
| 105 |
+
"airybiprime": [(lambda x: True, "AiryBiPrime")],
|
| 106 |
+
"polylog": [(lambda *x: True, "PolyLog")],
|
| 107 |
+
"lerchphi": [(lambda *x: True, "LerchPhi")],
|
| 108 |
+
"gcd": [(lambda *x: True, "GCD")],
|
| 109 |
+
"lcm": [(lambda *x: True, "LCM")],
|
| 110 |
+
"jn": [(lambda *x: True, "SphericalBesselJ")],
|
| 111 |
+
"yn": [(lambda *x: True, "SphericalBesselY")],
|
| 112 |
+
"hyper": [(lambda *x: True, "HypergeometricPFQ")],
|
| 113 |
+
"meijerg": [(lambda *x: True, "MeijerG")],
|
| 114 |
+
"appellf1": [(lambda *x: True, "AppellF1")],
|
| 115 |
+
"DiracDelta": [(lambda x: True, "DiracDelta")],
|
| 116 |
+
"Heaviside": [(lambda x: True, "HeavisideTheta")],
|
| 117 |
+
"KroneckerDelta": [(lambda *x: True, "KroneckerDelta")],
|
| 118 |
+
"sqrt": [(lambda x: True, "Sqrt")], # For automatic rewrites
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
class MCodePrinter(CodePrinter):
|
| 123 |
+
"""A printer to convert Python expressions to
|
| 124 |
+
strings of the Wolfram's Mathematica code
|
| 125 |
+
"""
|
| 126 |
+
printmethod = "_mcode"
|
| 127 |
+
language = "Wolfram Language"
|
| 128 |
+
|
| 129 |
+
_default_settings: dict[str, Any] = dict(CodePrinter._default_settings, **{
|
| 130 |
+
'precision': 15,
|
| 131 |
+
'user_functions': {},
|
| 132 |
+
})
|
| 133 |
+
|
| 134 |
+
_number_symbols: set[tuple[Expr, Float]] = set()
|
| 135 |
+
_not_supported: set[Basic] = set()
|
| 136 |
+
|
| 137 |
+
def __init__(self, settings={}):
|
| 138 |
+
"""Register function mappings supplied by user"""
|
| 139 |
+
CodePrinter.__init__(self, settings)
|
| 140 |
+
self.known_functions = dict(known_functions)
|
| 141 |
+
userfuncs = settings.get('user_functions', {}).copy()
|
| 142 |
+
for k, v in userfuncs.items():
|
| 143 |
+
if not isinstance(v, list):
|
| 144 |
+
userfuncs[k] = [(lambda *x: True, v)]
|
| 145 |
+
self.known_functions.update(userfuncs)
|
| 146 |
+
|
| 147 |
+
def _format_code(self, lines):
|
| 148 |
+
return lines
|
| 149 |
+
|
| 150 |
+
def _print_Pow(self, expr):
|
| 151 |
+
PREC = precedence(expr)
|
| 152 |
+
return '%s^%s' % (self.parenthesize(expr.base, PREC),
|
| 153 |
+
self.parenthesize(expr.exp, PREC))
|
| 154 |
+
|
| 155 |
+
def _print_Mul(self, expr):
|
| 156 |
+
PREC = precedence(expr)
|
| 157 |
+
c, nc = expr.args_cnc()
|
| 158 |
+
res = super()._print_Mul(expr.func(*c))
|
| 159 |
+
if nc:
|
| 160 |
+
res += '*'
|
| 161 |
+
res += '**'.join(self.parenthesize(a, PREC) for a in nc)
|
| 162 |
+
return res
|
| 163 |
+
|
| 164 |
+
def _print_Relational(self, expr):
|
| 165 |
+
lhs_code = self._print(expr.lhs)
|
| 166 |
+
rhs_code = self._print(expr.rhs)
|
| 167 |
+
op = expr.rel_op
|
| 168 |
+
return "{} {} {}".format(lhs_code, op, rhs_code)
|
| 169 |
+
|
| 170 |
+
# Primitive numbers
|
| 171 |
+
def _print_Zero(self, expr):
|
| 172 |
+
return '0'
|
| 173 |
+
|
| 174 |
+
def _print_One(self, expr):
|
| 175 |
+
return '1'
|
| 176 |
+
|
| 177 |
+
def _print_NegativeOne(self, expr):
|
| 178 |
+
return '-1'
|
| 179 |
+
|
| 180 |
+
def _print_Half(self, expr):
|
| 181 |
+
return '1/2'
|
| 182 |
+
|
| 183 |
+
def _print_ImaginaryUnit(self, expr):
|
| 184 |
+
return 'I'
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
# Infinity and invalid numbers
|
| 188 |
+
def _print_Infinity(self, expr):
|
| 189 |
+
return 'Infinity'
|
| 190 |
+
|
| 191 |
+
def _print_NegativeInfinity(self, expr):
|
| 192 |
+
return '-Infinity'
|
| 193 |
+
|
| 194 |
+
def _print_ComplexInfinity(self, expr):
|
| 195 |
+
return 'ComplexInfinity'
|
| 196 |
+
|
| 197 |
+
def _print_NaN(self, expr):
|
| 198 |
+
return 'Indeterminate'
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
# Mathematical constants
|
| 202 |
+
def _print_Exp1(self, expr):
|
| 203 |
+
return 'E'
|
| 204 |
+
|
| 205 |
+
def _print_Pi(self, expr):
|
| 206 |
+
return 'Pi'
|
| 207 |
+
|
| 208 |
+
def _print_GoldenRatio(self, expr):
|
| 209 |
+
return 'GoldenRatio'
|
| 210 |
+
|
| 211 |
+
def _print_TribonacciConstant(self, expr):
|
| 212 |
+
expanded = expr.expand(func=True)
|
| 213 |
+
PREC = precedence(expr)
|
| 214 |
+
return self.parenthesize(expanded, PREC)
|
| 215 |
+
|
| 216 |
+
def _print_EulerGamma(self, expr):
|
| 217 |
+
return 'EulerGamma'
|
| 218 |
+
|
| 219 |
+
def _print_Catalan(self, expr):
|
| 220 |
+
return 'Catalan'
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def _print_list(self, expr):
|
| 224 |
+
return '{' + ', '.join(self.doprint(a) for a in expr) + '}'
|
| 225 |
+
_print_tuple = _print_list
|
| 226 |
+
_print_Tuple = _print_list
|
| 227 |
+
|
| 228 |
+
def _print_ImmutableDenseMatrix(self, expr):
|
| 229 |
+
return self.doprint(expr.tolist())
|
| 230 |
+
|
| 231 |
+
def _print_ImmutableSparseMatrix(self, expr):
|
| 232 |
+
|
| 233 |
+
def print_rule(pos, val):
|
| 234 |
+
return '{} -> {}'.format(
|
| 235 |
+
self.doprint((pos[0]+1, pos[1]+1)), self.doprint(val))
|
| 236 |
+
|
| 237 |
+
def print_data():
|
| 238 |
+
items = sorted(expr.todok().items(), key=default_sort_key)
|
| 239 |
+
return '{' + \
|
| 240 |
+
', '.join(print_rule(k, v) for k, v in items) + \
|
| 241 |
+
'}'
|
| 242 |
+
|
| 243 |
+
def print_dims():
|
| 244 |
+
return self.doprint(expr.shape)
|
| 245 |
+
|
| 246 |
+
return 'SparseArray[{}, {}]'.format(print_data(), print_dims())
|
| 247 |
+
|
| 248 |
+
def _print_ImmutableDenseNDimArray(self, expr):
|
| 249 |
+
return self.doprint(expr.tolist())
|
| 250 |
+
|
| 251 |
+
def _print_ImmutableSparseNDimArray(self, expr):
|
| 252 |
+
def print_string_list(string_list):
|
| 253 |
+
return '{' + ', '.join(a for a in string_list) + '}'
|
| 254 |
+
|
| 255 |
+
def to_mathematica_index(*args):
|
| 256 |
+
"""Helper function to change Python style indexing to
|
| 257 |
+
Pathematica indexing.
|
| 258 |
+
|
| 259 |
+
Python indexing (0, 1 ... n-1)
|
| 260 |
+
-> Mathematica indexing (1, 2 ... n)
|
| 261 |
+
"""
|
| 262 |
+
return tuple(i + 1 for i in args)
|
| 263 |
+
|
| 264 |
+
def print_rule(pos, val):
|
| 265 |
+
"""Helper function to print a rule of Mathematica"""
|
| 266 |
+
return '{} -> {}'.format(self.doprint(pos), self.doprint(val))
|
| 267 |
+
|
| 268 |
+
def print_data():
|
| 269 |
+
"""Helper function to print data part of Mathematica
|
| 270 |
+
sparse array.
|
| 271 |
+
|
| 272 |
+
It uses the fourth notation ``SparseArray[data,{d1,d2,...}]``
|
| 273 |
+
from
|
| 274 |
+
https://reference.wolfram.com/language/ref/SparseArray.html
|
| 275 |
+
|
| 276 |
+
``data`` must be formatted with rule.
|
| 277 |
+
"""
|
| 278 |
+
return print_string_list(
|
| 279 |
+
[print_rule(
|
| 280 |
+
to_mathematica_index(*(expr._get_tuple_index(key))),
|
| 281 |
+
value)
|
| 282 |
+
for key, value in sorted(expr._sparse_array.items())]
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
def print_dims():
|
| 286 |
+
"""Helper function to print dimensions part of Mathematica
|
| 287 |
+
sparse array.
|
| 288 |
+
|
| 289 |
+
It uses the fourth notation ``SparseArray[data,{d1,d2,...}]``
|
| 290 |
+
from
|
| 291 |
+
https://reference.wolfram.com/language/ref/SparseArray.html
|
| 292 |
+
"""
|
| 293 |
+
return self.doprint(expr.shape)
|
| 294 |
+
|
| 295 |
+
return 'SparseArray[{}, {}]'.format(print_data(), print_dims())
|
| 296 |
+
|
| 297 |
+
def _print_Function(self, expr):
|
| 298 |
+
if expr.func.__name__ in self.known_functions:
|
| 299 |
+
cond_mfunc = self.known_functions[expr.func.__name__]
|
| 300 |
+
for cond, mfunc in cond_mfunc:
|
| 301 |
+
if cond(*expr.args):
|
| 302 |
+
return "%s[%s]" % (mfunc, self.stringify(expr.args, ", "))
|
| 303 |
+
elif expr.func.__name__ in self._rewriteable_functions:
|
| 304 |
+
# Simple rewrite to supported function possible
|
| 305 |
+
target_f, required_fs = self._rewriteable_functions[expr.func.__name__]
|
| 306 |
+
if self._can_print(target_f) and all(self._can_print(f) for f in required_fs):
|
| 307 |
+
return self._print(expr.rewrite(target_f))
|
| 308 |
+
return expr.func.__name__ + "[%s]" % self.stringify(expr.args, ", ")
|
| 309 |
+
|
| 310 |
+
_print_MinMaxBase = _print_Function
|
| 311 |
+
|
| 312 |
+
def _print_LambertW(self, expr):
|
| 313 |
+
if len(expr.args) == 1:
|
| 314 |
+
return "ProductLog[{}]".format(self._print(expr.args[0]))
|
| 315 |
+
return "ProductLog[{}, {}]".format(
|
| 316 |
+
self._print(expr.args[1]), self._print(expr.args[0]))
|
| 317 |
+
|
| 318 |
+
def _print_atan2(self, expr):
|
| 319 |
+
return "ArcTan[{}, {}]".format(
|
| 320 |
+
self._print(expr.args[1]), self._print(expr.args[0]))
|
| 321 |
+
|
| 322 |
+
def _print_Integral(self, expr):
|
| 323 |
+
if len(expr.variables) == 1 and not expr.limits[0][1:]:
|
| 324 |
+
args = [expr.args[0], expr.variables[0]]
|
| 325 |
+
else:
|
| 326 |
+
args = expr.args
|
| 327 |
+
return "Hold[Integrate[" + ', '.join(self.doprint(a) for a in args) + "]]"
|
| 328 |
+
|
| 329 |
+
def _print_Sum(self, expr):
|
| 330 |
+
return "Hold[Sum[" + ', '.join(self.doprint(a) for a in expr.args) + "]]"
|
| 331 |
+
|
| 332 |
+
def _print_Derivative(self, expr):
|
| 333 |
+
dexpr = expr.expr
|
| 334 |
+
dvars = [i[0] if i[1] == 1 else i for i in expr.variable_count]
|
| 335 |
+
return "Hold[D[" + ', '.join(self.doprint(a) for a in [dexpr] + dvars) + "]]"
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def _get_comment(self, text):
|
| 339 |
+
return "(* {} *)".format(text)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
def mathematica_code(expr, **settings):
|
| 343 |
+
r"""Converts an expr to a string of the Wolfram Mathematica code
|
| 344 |
+
|
| 345 |
+
Examples
|
| 346 |
+
========
|
| 347 |
+
|
| 348 |
+
>>> from sympy import mathematica_code as mcode, symbols, sin
|
| 349 |
+
>>> x = symbols('x')
|
| 350 |
+
>>> mcode(sin(x).series(x).removeO())
|
| 351 |
+
'(1/120)*x^5 - 1/6*x^3 + x'
|
| 352 |
+
"""
|
| 353 |
+
return MCodePrinter(settings).doprint(expr)
|
.venv/lib/python3.13/site-packages/sympy/printing/mathml.py
ADDED
|
@@ -0,0 +1,2157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
A MathML printer.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
from sympy.core.mul import Mul
|
| 9 |
+
from sympy.core.singleton import S
|
| 10 |
+
from sympy.core.sorting import default_sort_key
|
| 11 |
+
from sympy.core.sympify import sympify
|
| 12 |
+
from sympy.printing.conventions import split_super_sub, requires_partial
|
| 13 |
+
from sympy.printing.precedence import \
|
| 14 |
+
precedence_traditional, PRECEDENCE, PRECEDENCE_TRADITIONAL
|
| 15 |
+
from sympy.printing.pretty.pretty_symbology import greek_unicode
|
| 16 |
+
from sympy.printing.printer import Printer, print_function
|
| 17 |
+
|
| 18 |
+
from mpmath.libmp import prec_to_dps, repr_dps, to_str as mlib_to_str
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class MathMLPrinterBase(Printer):
|
| 22 |
+
"""Contains common code required for MathMLContentPrinter and
|
| 23 |
+
MathMLPresentationPrinter.
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
_default_settings: dict[str, Any] = {
|
| 27 |
+
"order": None,
|
| 28 |
+
"encoding": "utf-8",
|
| 29 |
+
"fold_frac_powers": False,
|
| 30 |
+
"fold_func_brackets": False,
|
| 31 |
+
"fold_short_frac": None,
|
| 32 |
+
"inv_trig_style": "abbreviated",
|
| 33 |
+
"ln_notation": False,
|
| 34 |
+
"long_frac_ratio": None,
|
| 35 |
+
"mat_delim": "[",
|
| 36 |
+
"mat_symbol_style": "plain",
|
| 37 |
+
"mul_symbol": None,
|
| 38 |
+
"root_notation": True,
|
| 39 |
+
"symbol_names": {},
|
| 40 |
+
"mul_symbol_mathml_numbers": '·',
|
| 41 |
+
"disable_split_super_sub": False,
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
def __init__(self, settings=None):
|
| 45 |
+
Printer.__init__(self, settings)
|
| 46 |
+
from xml.dom.minidom import Document, Text
|
| 47 |
+
|
| 48 |
+
self.dom = Document()
|
| 49 |
+
|
| 50 |
+
# Workaround to allow strings to remain unescaped
|
| 51 |
+
# Based on
|
| 52 |
+
# https://stackoverflow.com/questions/38015864/python-xml-dom-minidom-\
|
| 53 |
+
# please-dont-escape-my-strings/38041194
|
| 54 |
+
class RawText(Text):
|
| 55 |
+
def writexml(self, writer, indent='', addindent='', newl=''):
|
| 56 |
+
if self.data:
|
| 57 |
+
writer.write('{}{}{}'.format(indent, self.data, newl))
|
| 58 |
+
|
| 59 |
+
def createRawTextNode(data):
|
| 60 |
+
r = RawText()
|
| 61 |
+
r.data = data
|
| 62 |
+
r.ownerDocument = self.dom
|
| 63 |
+
return r
|
| 64 |
+
|
| 65 |
+
self.dom.createTextNode = createRawTextNode
|
| 66 |
+
|
| 67 |
+
def doprint(self, expr):
|
| 68 |
+
"""
|
| 69 |
+
Prints the expression as MathML.
|
| 70 |
+
"""
|
| 71 |
+
mathML = Printer._print(self, expr)
|
| 72 |
+
unistr = mathML.toxml()
|
| 73 |
+
xmlbstr = unistr.encode('ascii', 'xmlcharrefreplace')
|
| 74 |
+
res = xmlbstr.decode()
|
| 75 |
+
return res
|
| 76 |
+
|
| 77 |
+
def _split_super_sub(self, name):
|
| 78 |
+
if self._settings["disable_split_super_sub"]:
|
| 79 |
+
return (name, [], [])
|
| 80 |
+
else:
|
| 81 |
+
return split_super_sub(name)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
class MathMLContentPrinter(MathMLPrinterBase):
|
| 85 |
+
"""Prints an expression to the Content MathML markup language.
|
| 86 |
+
|
| 87 |
+
References: https://www.w3.org/TR/MathML2/chapter4.html
|
| 88 |
+
"""
|
| 89 |
+
printmethod = "_mathml_content"
|
| 90 |
+
|
| 91 |
+
def mathml_tag(self, e):
|
| 92 |
+
"""Returns the MathML tag for an expression."""
|
| 93 |
+
translate = {
|
| 94 |
+
'Add': 'plus',
|
| 95 |
+
'Mul': 'times',
|
| 96 |
+
'Derivative': 'diff',
|
| 97 |
+
'Number': 'cn',
|
| 98 |
+
'int': 'cn',
|
| 99 |
+
'Pow': 'power',
|
| 100 |
+
'Max': 'max',
|
| 101 |
+
'Min': 'min',
|
| 102 |
+
'Abs': 'abs',
|
| 103 |
+
'And': 'and',
|
| 104 |
+
'Or': 'or',
|
| 105 |
+
'Xor': 'xor',
|
| 106 |
+
'Not': 'not',
|
| 107 |
+
'Implies': 'implies',
|
| 108 |
+
'Symbol': 'ci',
|
| 109 |
+
'MatrixSymbol': 'ci',
|
| 110 |
+
'RandomSymbol': 'ci',
|
| 111 |
+
'Integral': 'int',
|
| 112 |
+
'Sum': 'sum',
|
| 113 |
+
'sin': 'sin',
|
| 114 |
+
'cos': 'cos',
|
| 115 |
+
'tan': 'tan',
|
| 116 |
+
'cot': 'cot',
|
| 117 |
+
'csc': 'csc',
|
| 118 |
+
'sec': 'sec',
|
| 119 |
+
'sinh': 'sinh',
|
| 120 |
+
'cosh': 'cosh',
|
| 121 |
+
'tanh': 'tanh',
|
| 122 |
+
'coth': 'coth',
|
| 123 |
+
'csch': 'csch',
|
| 124 |
+
'sech': 'sech',
|
| 125 |
+
'asin': 'arcsin',
|
| 126 |
+
'asinh': 'arcsinh',
|
| 127 |
+
'acos': 'arccos',
|
| 128 |
+
'acosh': 'arccosh',
|
| 129 |
+
'atan': 'arctan',
|
| 130 |
+
'atanh': 'arctanh',
|
| 131 |
+
'atan2': 'arctan',
|
| 132 |
+
'acot': 'arccot',
|
| 133 |
+
'acoth': 'arccoth',
|
| 134 |
+
'asec': 'arcsec',
|
| 135 |
+
'asech': 'arcsech',
|
| 136 |
+
'acsc': 'arccsc',
|
| 137 |
+
'acsch': 'arccsch',
|
| 138 |
+
'log': 'ln',
|
| 139 |
+
'Equality': 'eq',
|
| 140 |
+
'Unequality': 'neq',
|
| 141 |
+
'GreaterThan': 'geq',
|
| 142 |
+
'LessThan': 'leq',
|
| 143 |
+
'StrictGreaterThan': 'gt',
|
| 144 |
+
'StrictLessThan': 'lt',
|
| 145 |
+
'Union': 'union',
|
| 146 |
+
'Intersection': 'intersect',
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
for cls in e.__class__.__mro__:
|
| 150 |
+
n = cls.__name__
|
| 151 |
+
if n in translate:
|
| 152 |
+
return translate[n]
|
| 153 |
+
# Not found in the MRO set
|
| 154 |
+
n = e.__class__.__name__
|
| 155 |
+
return n.lower()
|
| 156 |
+
|
| 157 |
+
def _print_Mul(self, expr):
|
| 158 |
+
|
| 159 |
+
if expr.could_extract_minus_sign():
|
| 160 |
+
x = self.dom.createElement('apply')
|
| 161 |
+
x.appendChild(self.dom.createElement('minus'))
|
| 162 |
+
x.appendChild(self._print_Mul(-expr))
|
| 163 |
+
return x
|
| 164 |
+
|
| 165 |
+
from sympy.simplify import fraction
|
| 166 |
+
numer, denom = fraction(expr)
|
| 167 |
+
|
| 168 |
+
if denom is not S.One:
|
| 169 |
+
x = self.dom.createElement('apply')
|
| 170 |
+
x.appendChild(self.dom.createElement('divide'))
|
| 171 |
+
x.appendChild(self._print(numer))
|
| 172 |
+
x.appendChild(self._print(denom))
|
| 173 |
+
return x
|
| 174 |
+
|
| 175 |
+
coeff, terms = expr.as_coeff_mul()
|
| 176 |
+
if coeff is S.One and len(terms) == 1:
|
| 177 |
+
# XXX since the negative coefficient has been handled, I don't
|
| 178 |
+
# think a coeff of 1 can remain
|
| 179 |
+
return self._print(terms[0])
|
| 180 |
+
|
| 181 |
+
if self.order != 'old':
|
| 182 |
+
terms = Mul._from_args(terms).as_ordered_factors()
|
| 183 |
+
|
| 184 |
+
x = self.dom.createElement('apply')
|
| 185 |
+
x.appendChild(self.dom.createElement('times'))
|
| 186 |
+
if coeff != 1:
|
| 187 |
+
x.appendChild(self._print(coeff))
|
| 188 |
+
for term in terms:
|
| 189 |
+
x.appendChild(self._print(term))
|
| 190 |
+
return x
|
| 191 |
+
|
| 192 |
+
def _print_Add(self, expr, order=None):
|
| 193 |
+
args = self._as_ordered_terms(expr, order=order)
|
| 194 |
+
lastProcessed = self._print(args[0])
|
| 195 |
+
plusNodes = []
|
| 196 |
+
for arg in args[1:]:
|
| 197 |
+
if arg.could_extract_minus_sign():
|
| 198 |
+
# use minus
|
| 199 |
+
x = self.dom.createElement('apply')
|
| 200 |
+
x.appendChild(self.dom.createElement('minus'))
|
| 201 |
+
x.appendChild(lastProcessed)
|
| 202 |
+
x.appendChild(self._print(-arg))
|
| 203 |
+
# invert expression since this is now minused
|
| 204 |
+
lastProcessed = x
|
| 205 |
+
if arg == args[-1]:
|
| 206 |
+
plusNodes.append(lastProcessed)
|
| 207 |
+
else:
|
| 208 |
+
plusNodes.append(lastProcessed)
|
| 209 |
+
lastProcessed = self._print(arg)
|
| 210 |
+
if arg == args[-1]:
|
| 211 |
+
plusNodes.append(self._print(arg))
|
| 212 |
+
if len(plusNodes) == 1:
|
| 213 |
+
return lastProcessed
|
| 214 |
+
x = self.dom.createElement('apply')
|
| 215 |
+
x.appendChild(self.dom.createElement('plus'))
|
| 216 |
+
while plusNodes:
|
| 217 |
+
x.appendChild(plusNodes.pop(0))
|
| 218 |
+
return x
|
| 219 |
+
|
| 220 |
+
def _print_Piecewise(self, expr):
|
| 221 |
+
if expr.args[-1].cond != True:
|
| 222 |
+
# We need the last conditional to be a True, otherwise the resulting
|
| 223 |
+
# function may not return a result.
|
| 224 |
+
raise ValueError("All Piecewise expressions must contain an "
|
| 225 |
+
"(expr, True) statement to be used as a default "
|
| 226 |
+
"condition. Without one, the generated "
|
| 227 |
+
"expression may not evaluate to anything under "
|
| 228 |
+
"some condition.")
|
| 229 |
+
root = self.dom.createElement('piecewise')
|
| 230 |
+
for i, (e, c) in enumerate(expr.args):
|
| 231 |
+
if i == len(expr.args) - 1 and c == True:
|
| 232 |
+
piece = self.dom.createElement('otherwise')
|
| 233 |
+
piece.appendChild(self._print(e))
|
| 234 |
+
else:
|
| 235 |
+
piece = self.dom.createElement('piece')
|
| 236 |
+
piece.appendChild(self._print(e))
|
| 237 |
+
piece.appendChild(self._print(c))
|
| 238 |
+
root.appendChild(piece)
|
| 239 |
+
return root
|
| 240 |
+
|
| 241 |
+
def _print_MatrixBase(self, m):
|
| 242 |
+
x = self.dom.createElement('matrix')
|
| 243 |
+
for i in range(m.rows):
|
| 244 |
+
x_r = self.dom.createElement('matrixrow')
|
| 245 |
+
for j in range(m.cols):
|
| 246 |
+
x_r.appendChild(self._print(m[i, j]))
|
| 247 |
+
x.appendChild(x_r)
|
| 248 |
+
return x
|
| 249 |
+
|
| 250 |
+
def _print_Rational(self, e):
|
| 251 |
+
if e.q == 1:
|
| 252 |
+
# don't divide
|
| 253 |
+
x = self.dom.createElement('cn')
|
| 254 |
+
x.appendChild(self.dom.createTextNode(str(e.p)))
|
| 255 |
+
return x
|
| 256 |
+
x = self.dom.createElement('apply')
|
| 257 |
+
x.appendChild(self.dom.createElement('divide'))
|
| 258 |
+
# numerator
|
| 259 |
+
xnum = self.dom.createElement('cn')
|
| 260 |
+
xnum.appendChild(self.dom.createTextNode(str(e.p)))
|
| 261 |
+
# denominator
|
| 262 |
+
xdenom = self.dom.createElement('cn')
|
| 263 |
+
xdenom.appendChild(self.dom.createTextNode(str(e.q)))
|
| 264 |
+
x.appendChild(xnum)
|
| 265 |
+
x.appendChild(xdenom)
|
| 266 |
+
return x
|
| 267 |
+
|
| 268 |
+
def _print_Limit(self, e):
|
| 269 |
+
x = self.dom.createElement('apply')
|
| 270 |
+
x.appendChild(self.dom.createElement(self.mathml_tag(e)))
|
| 271 |
+
|
| 272 |
+
x_1 = self.dom.createElement('bvar')
|
| 273 |
+
x_2 = self.dom.createElement('lowlimit')
|
| 274 |
+
x_1.appendChild(self._print(e.args[1]))
|
| 275 |
+
x_2.appendChild(self._print(e.args[2]))
|
| 276 |
+
|
| 277 |
+
x.appendChild(x_1)
|
| 278 |
+
x.appendChild(x_2)
|
| 279 |
+
x.appendChild(self._print(e.args[0]))
|
| 280 |
+
return x
|
| 281 |
+
|
| 282 |
+
def _print_ImaginaryUnit(self, e):
|
| 283 |
+
return self.dom.createElement('imaginaryi')
|
| 284 |
+
|
| 285 |
+
def _print_EulerGamma(self, e):
|
| 286 |
+
return self.dom.createElement('eulergamma')
|
| 287 |
+
|
| 288 |
+
def _print_GoldenRatio(self, e):
|
| 289 |
+
"""We use unicode #x3c6 for Greek letter phi as defined here
|
| 290 |
+
https://www.w3.org/2003/entities/2007doc/isogrk1.html"""
|
| 291 |
+
x = self.dom.createElement('cn')
|
| 292 |
+
x.appendChild(self.dom.createTextNode("\N{GREEK SMALL LETTER PHI}"))
|
| 293 |
+
return x
|
| 294 |
+
|
| 295 |
+
def _print_Exp1(self, e):
|
| 296 |
+
return self.dom.createElement('exponentiale')
|
| 297 |
+
|
| 298 |
+
def _print_Pi(self, e):
|
| 299 |
+
return self.dom.createElement('pi')
|
| 300 |
+
|
| 301 |
+
def _print_Infinity(self, e):
|
| 302 |
+
return self.dom.createElement('infinity')
|
| 303 |
+
|
| 304 |
+
def _print_NaN(self, e):
|
| 305 |
+
return self.dom.createElement('notanumber')
|
| 306 |
+
|
| 307 |
+
def _print_EmptySet(self, e):
|
| 308 |
+
return self.dom.createElement('emptyset')
|
| 309 |
+
|
| 310 |
+
def _print_BooleanTrue(self, e):
|
| 311 |
+
return self.dom.createElement('true')
|
| 312 |
+
|
| 313 |
+
def _print_BooleanFalse(self, e):
|
| 314 |
+
return self.dom.createElement('false')
|
| 315 |
+
|
| 316 |
+
def _print_NegativeInfinity(self, e):
|
| 317 |
+
x = self.dom.createElement('apply')
|
| 318 |
+
x.appendChild(self.dom.createElement('minus'))
|
| 319 |
+
x.appendChild(self.dom.createElement('infinity'))
|
| 320 |
+
return x
|
| 321 |
+
|
| 322 |
+
def _print_Integral(self, e):
|
| 323 |
+
def lime_recur(limits):
|
| 324 |
+
x = self.dom.createElement('apply')
|
| 325 |
+
x.appendChild(self.dom.createElement(self.mathml_tag(e)))
|
| 326 |
+
bvar_elem = self.dom.createElement('bvar')
|
| 327 |
+
bvar_elem.appendChild(self._print(limits[0][0]))
|
| 328 |
+
x.appendChild(bvar_elem)
|
| 329 |
+
|
| 330 |
+
if len(limits[0]) == 3:
|
| 331 |
+
low_elem = self.dom.createElement('lowlimit')
|
| 332 |
+
low_elem.appendChild(self._print(limits[0][1]))
|
| 333 |
+
x.appendChild(low_elem)
|
| 334 |
+
up_elem = self.dom.createElement('uplimit')
|
| 335 |
+
up_elem.appendChild(self._print(limits[0][2]))
|
| 336 |
+
x.appendChild(up_elem)
|
| 337 |
+
if len(limits[0]) == 2:
|
| 338 |
+
up_elem = self.dom.createElement('uplimit')
|
| 339 |
+
up_elem.appendChild(self._print(limits[0][1]))
|
| 340 |
+
x.appendChild(up_elem)
|
| 341 |
+
if len(limits) == 1:
|
| 342 |
+
x.appendChild(self._print(e.function))
|
| 343 |
+
else:
|
| 344 |
+
x.appendChild(lime_recur(limits[1:]))
|
| 345 |
+
return x
|
| 346 |
+
|
| 347 |
+
limits = list(e.limits)
|
| 348 |
+
limits.reverse()
|
| 349 |
+
return lime_recur(limits)
|
| 350 |
+
|
| 351 |
+
def _print_Sum(self, e):
|
| 352 |
+
# Printer can be shared because Sum and Integral have the
|
| 353 |
+
# same internal representation.
|
| 354 |
+
return self._print_Integral(e)
|
| 355 |
+
|
| 356 |
+
def _print_Symbol(self, sym):
|
| 357 |
+
ci = self.dom.createElement(self.mathml_tag(sym))
|
| 358 |
+
|
| 359 |
+
def join(items):
|
| 360 |
+
if len(items) > 1:
|
| 361 |
+
mrow = self.dom.createElement('mml:mrow')
|
| 362 |
+
for i, item in enumerate(items):
|
| 363 |
+
if i > 0:
|
| 364 |
+
mo = self.dom.createElement('mml:mo')
|
| 365 |
+
mo.appendChild(self.dom.createTextNode(" "))
|
| 366 |
+
mrow.appendChild(mo)
|
| 367 |
+
mi = self.dom.createElement('mml:mi')
|
| 368 |
+
mi.appendChild(self.dom.createTextNode(item))
|
| 369 |
+
mrow.appendChild(mi)
|
| 370 |
+
return mrow
|
| 371 |
+
else:
|
| 372 |
+
mi = self.dom.createElement('mml:mi')
|
| 373 |
+
mi.appendChild(self.dom.createTextNode(items[0]))
|
| 374 |
+
return mi
|
| 375 |
+
|
| 376 |
+
# translate name, supers and subs to unicode characters
|
| 377 |
+
def translate(s):
|
| 378 |
+
if s in greek_unicode:
|
| 379 |
+
return greek_unicode.get(s)
|
| 380 |
+
else:
|
| 381 |
+
return s
|
| 382 |
+
|
| 383 |
+
name, supers, subs = self._split_super_sub(sym.name)
|
| 384 |
+
name = translate(name)
|
| 385 |
+
supers = [translate(sup) for sup in supers]
|
| 386 |
+
subs = [translate(sub) for sub in subs]
|
| 387 |
+
|
| 388 |
+
mname = self.dom.createElement('mml:mi')
|
| 389 |
+
mname.appendChild(self.dom.createTextNode(name))
|
| 390 |
+
if not supers:
|
| 391 |
+
if not subs:
|
| 392 |
+
ci.appendChild(self.dom.createTextNode(name))
|
| 393 |
+
else:
|
| 394 |
+
msub = self.dom.createElement('mml:msub')
|
| 395 |
+
msub.appendChild(mname)
|
| 396 |
+
msub.appendChild(join(subs))
|
| 397 |
+
ci.appendChild(msub)
|
| 398 |
+
else:
|
| 399 |
+
if not subs:
|
| 400 |
+
msup = self.dom.createElement('mml:msup')
|
| 401 |
+
msup.appendChild(mname)
|
| 402 |
+
msup.appendChild(join(supers))
|
| 403 |
+
ci.appendChild(msup)
|
| 404 |
+
else:
|
| 405 |
+
msubsup = self.dom.createElement('mml:msubsup')
|
| 406 |
+
msubsup.appendChild(mname)
|
| 407 |
+
msubsup.appendChild(join(subs))
|
| 408 |
+
msubsup.appendChild(join(supers))
|
| 409 |
+
ci.appendChild(msubsup)
|
| 410 |
+
return ci
|
| 411 |
+
|
| 412 |
+
_print_MatrixSymbol = _print_Symbol
|
| 413 |
+
_print_RandomSymbol = _print_Symbol
|
| 414 |
+
|
| 415 |
+
def _print_Pow(self, e):
|
| 416 |
+
# Here we use root instead of power if the exponent is the reciprocal
|
| 417 |
+
# of an integer
|
| 418 |
+
if (self._settings['root_notation'] and e.exp.is_Rational
|
| 419 |
+
and e.exp.p == 1):
|
| 420 |
+
x = self.dom.createElement('apply')
|
| 421 |
+
x.appendChild(self.dom.createElement('root'))
|
| 422 |
+
if e.exp.q != 2:
|
| 423 |
+
xmldeg = self.dom.createElement('degree')
|
| 424 |
+
xmlcn = self.dom.createElement('cn')
|
| 425 |
+
xmlcn.appendChild(self.dom.createTextNode(str(e.exp.q)))
|
| 426 |
+
xmldeg.appendChild(xmlcn)
|
| 427 |
+
x.appendChild(xmldeg)
|
| 428 |
+
x.appendChild(self._print(e.base))
|
| 429 |
+
return x
|
| 430 |
+
|
| 431 |
+
x = self.dom.createElement('apply')
|
| 432 |
+
x_1 = self.dom.createElement(self.mathml_tag(e))
|
| 433 |
+
x.appendChild(x_1)
|
| 434 |
+
x.appendChild(self._print(e.base))
|
| 435 |
+
x.appendChild(self._print(e.exp))
|
| 436 |
+
return x
|
| 437 |
+
|
| 438 |
+
def _print_Number(self, e):
|
| 439 |
+
x = self.dom.createElement(self.mathml_tag(e))
|
| 440 |
+
x.appendChild(self.dom.createTextNode(str(e)))
|
| 441 |
+
return x
|
| 442 |
+
|
| 443 |
+
def _print_Float(self, e):
|
| 444 |
+
x = self.dom.createElement(self.mathml_tag(e))
|
| 445 |
+
repr_e = mlib_to_str(e._mpf_, repr_dps(e._prec))
|
| 446 |
+
x.appendChild(self.dom.createTextNode(repr_e))
|
| 447 |
+
return x
|
| 448 |
+
|
| 449 |
+
def _print_Derivative(self, e):
|
| 450 |
+
x = self.dom.createElement('apply')
|
| 451 |
+
diff_symbol = self.mathml_tag(e)
|
| 452 |
+
if requires_partial(e.expr):
|
| 453 |
+
diff_symbol = 'partialdiff'
|
| 454 |
+
x.appendChild(self.dom.createElement(diff_symbol))
|
| 455 |
+
x_1 = self.dom.createElement('bvar')
|
| 456 |
+
|
| 457 |
+
for sym, times in reversed(e.variable_count):
|
| 458 |
+
x_1.appendChild(self._print(sym))
|
| 459 |
+
if times > 1:
|
| 460 |
+
degree = self.dom.createElement('degree')
|
| 461 |
+
degree.appendChild(self._print(sympify(times)))
|
| 462 |
+
x_1.appendChild(degree)
|
| 463 |
+
|
| 464 |
+
x.appendChild(x_1)
|
| 465 |
+
x.appendChild(self._print(e.expr))
|
| 466 |
+
return x
|
| 467 |
+
|
| 468 |
+
def _print_Function(self, e):
|
| 469 |
+
x = self.dom.createElement("apply")
|
| 470 |
+
x.appendChild(self.dom.createElement(self.mathml_tag(e)))
|
| 471 |
+
for arg in e.args:
|
| 472 |
+
x.appendChild(self._print(arg))
|
| 473 |
+
return x
|
| 474 |
+
|
| 475 |
+
def _print_Basic(self, e):
|
| 476 |
+
x = self.dom.createElement(self.mathml_tag(e))
|
| 477 |
+
for arg in e.args:
|
| 478 |
+
x.appendChild(self._print(arg))
|
| 479 |
+
return x
|
| 480 |
+
|
| 481 |
+
def _print_AssocOp(self, e):
|
| 482 |
+
x = self.dom.createElement('apply')
|
| 483 |
+
x_1 = self.dom.createElement(self.mathml_tag(e))
|
| 484 |
+
x.appendChild(x_1)
|
| 485 |
+
for arg in e.args:
|
| 486 |
+
x.appendChild(self._print(arg))
|
| 487 |
+
return x
|
| 488 |
+
|
| 489 |
+
def _print_Relational(self, e):
|
| 490 |
+
x = self.dom.createElement('apply')
|
| 491 |
+
x.appendChild(self.dom.createElement(self.mathml_tag(e)))
|
| 492 |
+
x.appendChild(self._print(e.lhs))
|
| 493 |
+
x.appendChild(self._print(e.rhs))
|
| 494 |
+
return x
|
| 495 |
+
|
| 496 |
+
def _print_list(self, seq):
|
| 497 |
+
"""MathML reference for the <list> element:
|
| 498 |
+
https://www.w3.org/TR/MathML2/chapter4.html#contm.list"""
|
| 499 |
+
dom_element = self.dom.createElement('list')
|
| 500 |
+
for item in seq:
|
| 501 |
+
dom_element.appendChild(self._print(item))
|
| 502 |
+
return dom_element
|
| 503 |
+
|
| 504 |
+
def _print_int(self, p):
|
| 505 |
+
dom_element = self.dom.createElement(self.mathml_tag(p))
|
| 506 |
+
dom_element.appendChild(self.dom.createTextNode(str(p)))
|
| 507 |
+
return dom_element
|
| 508 |
+
|
| 509 |
+
_print_Implies = _print_AssocOp
|
| 510 |
+
_print_Not = _print_AssocOp
|
| 511 |
+
_print_Xor = _print_AssocOp
|
| 512 |
+
|
| 513 |
+
def _print_FiniteSet(self, e):
|
| 514 |
+
x = self.dom.createElement('set')
|
| 515 |
+
for arg in e.args:
|
| 516 |
+
x.appendChild(self._print(arg))
|
| 517 |
+
return x
|
| 518 |
+
|
| 519 |
+
def _print_Complement(self, e):
|
| 520 |
+
x = self.dom.createElement('apply')
|
| 521 |
+
x.appendChild(self.dom.createElement('setdiff'))
|
| 522 |
+
for arg in e.args:
|
| 523 |
+
x.appendChild(self._print(arg))
|
| 524 |
+
return x
|
| 525 |
+
|
| 526 |
+
def _print_ProductSet(self, e):
|
| 527 |
+
x = self.dom.createElement('apply')
|
| 528 |
+
x.appendChild(self.dom.createElement('cartesianproduct'))
|
| 529 |
+
for arg in e.args:
|
| 530 |
+
x.appendChild(self._print(arg))
|
| 531 |
+
return x
|
| 532 |
+
|
| 533 |
+
def _print_Lambda(self, e):
|
| 534 |
+
# MathML reference for the lambda element:
|
| 535 |
+
# https://www.w3.org/TR/MathML2/chapter4.html#id.4.2.1.7
|
| 536 |
+
x = self.dom.createElement(self.mathml_tag(e))
|
| 537 |
+
for arg in e.signature:
|
| 538 |
+
x_1 = self.dom.createElement('bvar')
|
| 539 |
+
x_1.appendChild(self._print(arg))
|
| 540 |
+
x.appendChild(x_1)
|
| 541 |
+
x.appendChild(self._print(e.expr))
|
| 542 |
+
return x
|
| 543 |
+
|
| 544 |
+
# XXX Symmetric difference is not supported for MathML content printers.
|
| 545 |
+
|
| 546 |
+
|
| 547 |
+
class MathMLPresentationPrinter(MathMLPrinterBase):
|
| 548 |
+
"""Prints an expression to the Presentation MathML markup language.
|
| 549 |
+
|
| 550 |
+
References: https://www.w3.org/TR/MathML2/chapter3.html
|
| 551 |
+
"""
|
| 552 |
+
printmethod = "_mathml_presentation"
|
| 553 |
+
|
| 554 |
+
def mathml_tag(self, e):
|
| 555 |
+
"""Returns the MathML tag for an expression."""
|
| 556 |
+
translate = {
|
| 557 |
+
'Number': 'mn',
|
| 558 |
+
'Limit': '→',
|
| 559 |
+
'Derivative': 'ⅆ',
|
| 560 |
+
'int': 'mn',
|
| 561 |
+
'Symbol': 'mi',
|
| 562 |
+
'Integral': '∫',
|
| 563 |
+
'Sum': '∑',
|
| 564 |
+
'sin': 'sin',
|
| 565 |
+
'cos': 'cos',
|
| 566 |
+
'tan': 'tan',
|
| 567 |
+
'cot': 'cot',
|
| 568 |
+
'asin': 'arcsin',
|
| 569 |
+
'asinh': 'arcsinh',
|
| 570 |
+
'acos': 'arccos',
|
| 571 |
+
'acosh': 'arccosh',
|
| 572 |
+
'atan': 'arctan',
|
| 573 |
+
'atanh': 'arctanh',
|
| 574 |
+
'acot': 'arccot',
|
| 575 |
+
'atan2': 'arctan',
|
| 576 |
+
'Equality': '=',
|
| 577 |
+
'Unequality': '≠',
|
| 578 |
+
'GreaterThan': '≥',
|
| 579 |
+
'LessThan': '≤',
|
| 580 |
+
'StrictGreaterThan': '>',
|
| 581 |
+
'StrictLessThan': '<',
|
| 582 |
+
'lerchphi': 'Φ',
|
| 583 |
+
'zeta': 'ζ',
|
| 584 |
+
'dirichlet_eta': 'η',
|
| 585 |
+
'elliptic_k': 'Κ',
|
| 586 |
+
'lowergamma': 'γ',
|
| 587 |
+
'uppergamma': 'Γ',
|
| 588 |
+
'gamma': 'Γ',
|
| 589 |
+
'totient': 'ϕ',
|
| 590 |
+
'reduced_totient': 'λ',
|
| 591 |
+
'primenu': 'ν',
|
| 592 |
+
'primeomega': 'Ω',
|
| 593 |
+
'fresnels': 'S',
|
| 594 |
+
'fresnelc': 'C',
|
| 595 |
+
'LambertW': 'W',
|
| 596 |
+
'Heaviside': 'Θ',
|
| 597 |
+
'BooleanTrue': 'True',
|
| 598 |
+
'BooleanFalse': 'False',
|
| 599 |
+
'NoneType': 'None',
|
| 600 |
+
'mathieus': 'S',
|
| 601 |
+
'mathieuc': 'C',
|
| 602 |
+
'mathieusprime': 'S′',
|
| 603 |
+
'mathieucprime': 'C′',
|
| 604 |
+
'Lambda': 'lambda',
|
| 605 |
+
}
|
| 606 |
+
|
| 607 |
+
def mul_symbol_selection():
|
| 608 |
+
if (self._settings["mul_symbol"] is None or
|
| 609 |
+
self._settings["mul_symbol"] == 'None'):
|
| 610 |
+
return '⁢'
|
| 611 |
+
elif self._settings["mul_symbol"] == 'times':
|
| 612 |
+
return '×'
|
| 613 |
+
elif self._settings["mul_symbol"] == 'dot':
|
| 614 |
+
return '·'
|
| 615 |
+
elif self._settings["mul_symbol"] == 'ldot':
|
| 616 |
+
return '․'
|
| 617 |
+
elif not isinstance(self._settings["mul_symbol"], str):
|
| 618 |
+
raise TypeError
|
| 619 |
+
else:
|
| 620 |
+
return self._settings["mul_symbol"]
|
| 621 |
+
for cls in e.__class__.__mro__:
|
| 622 |
+
n = cls.__name__
|
| 623 |
+
if n in translate:
|
| 624 |
+
return translate[n]
|
| 625 |
+
# Not found in the MRO set
|
| 626 |
+
if e.__class__.__name__ == "Mul":
|
| 627 |
+
return mul_symbol_selection()
|
| 628 |
+
n = e.__class__.__name__
|
| 629 |
+
return n.lower()
|
| 630 |
+
|
| 631 |
+
def _l_paren(self):
|
| 632 |
+
mo = self.dom.createElement('mo')
|
| 633 |
+
mo.appendChild(self.dom.createTextNode('('))
|
| 634 |
+
return mo
|
| 635 |
+
|
| 636 |
+
def _r_paren(self):
|
| 637 |
+
mo = self.dom.createElement('mo')
|
| 638 |
+
mo.appendChild(self.dom.createTextNode(')'))
|
| 639 |
+
return mo
|
| 640 |
+
|
| 641 |
+
def _l_brace(self):
|
| 642 |
+
mo = self.dom.createElement('mo')
|
| 643 |
+
mo.appendChild(self.dom.createTextNode('{'))
|
| 644 |
+
return mo
|
| 645 |
+
|
| 646 |
+
def _r_brace(self):
|
| 647 |
+
mo = self.dom.createElement('mo')
|
| 648 |
+
mo.appendChild(self.dom.createTextNode('}'))
|
| 649 |
+
return mo
|
| 650 |
+
|
| 651 |
+
def _comma(self):
|
| 652 |
+
mo = self.dom.createElement('mo')
|
| 653 |
+
mo.appendChild(self.dom.createTextNode(','))
|
| 654 |
+
return mo
|
| 655 |
+
|
| 656 |
+
def _bar(self):
|
| 657 |
+
mo = self.dom.createElement('mo')
|
| 658 |
+
mo.appendChild(self.dom.createTextNode('|'))
|
| 659 |
+
return mo
|
| 660 |
+
|
| 661 |
+
def _semicolon(self):
|
| 662 |
+
mo = self.dom.createElement('mo')
|
| 663 |
+
mo.appendChild(self.dom.createTextNode(';'))
|
| 664 |
+
return mo
|
| 665 |
+
|
| 666 |
+
def _paren_comma_separated(self, *args):
|
| 667 |
+
mrow = self.dom.createElement('mrow')
|
| 668 |
+
mrow.appendChild(self._l_paren())
|
| 669 |
+
for i, arg in enumerate(args):
|
| 670 |
+
if i:
|
| 671 |
+
mrow.appendChild(self._comma())
|
| 672 |
+
mrow.appendChild(self._print(arg))
|
| 673 |
+
mrow.appendChild(self._r_paren())
|
| 674 |
+
return mrow
|
| 675 |
+
|
| 676 |
+
def _paren_bar_separated(self, *args):
|
| 677 |
+
mrow = self.dom.createElement('mrow')
|
| 678 |
+
mrow.appendChild(self._l_paren())
|
| 679 |
+
for i, arg in enumerate(args):
|
| 680 |
+
if i:
|
| 681 |
+
mrow.appendChild(self._bar())
|
| 682 |
+
mrow.appendChild(self._print(arg))
|
| 683 |
+
mrow.appendChild(self._r_paren())
|
| 684 |
+
return mrow
|
| 685 |
+
|
| 686 |
+
def parenthesize(self, item, level, strict=False):
|
| 687 |
+
prec_val = precedence_traditional(item)
|
| 688 |
+
if (prec_val < level) or ((not strict) and prec_val <= level):
|
| 689 |
+
mrow = self.dom.createElement('mrow')
|
| 690 |
+
mrow.appendChild(self._l_paren())
|
| 691 |
+
mrow.appendChild(self._print(item))
|
| 692 |
+
mrow.appendChild(self._r_paren())
|
| 693 |
+
return mrow
|
| 694 |
+
return self._print(item)
|
| 695 |
+
|
| 696 |
+
def _print_Mul(self, expr):
|
| 697 |
+
|
| 698 |
+
def multiply(expr, mrow):
|
| 699 |
+
from sympy.simplify import fraction
|
| 700 |
+
numer, denom = fraction(expr)
|
| 701 |
+
if denom is not S.One:
|
| 702 |
+
frac = self.dom.createElement('mfrac')
|
| 703 |
+
if self._settings["fold_short_frac"] and len(str(expr)) < 7:
|
| 704 |
+
frac.setAttribute('bevelled', 'true')
|
| 705 |
+
xnum = self._print(numer)
|
| 706 |
+
xden = self._print(denom)
|
| 707 |
+
frac.appendChild(xnum)
|
| 708 |
+
frac.appendChild(xden)
|
| 709 |
+
mrow.appendChild(frac)
|
| 710 |
+
return mrow
|
| 711 |
+
|
| 712 |
+
coeff, terms = expr.as_coeff_mul()
|
| 713 |
+
if coeff is S.One and len(terms) == 1:
|
| 714 |
+
mrow.appendChild(self._print(terms[0]))
|
| 715 |
+
return mrow
|
| 716 |
+
if self.order != 'old':
|
| 717 |
+
terms = Mul._from_args(terms).as_ordered_factors()
|
| 718 |
+
|
| 719 |
+
if coeff != 1:
|
| 720 |
+
x = self._print(coeff)
|
| 721 |
+
y = self.dom.createElement('mo')
|
| 722 |
+
y.appendChild(self.dom.createTextNode(self.mathml_tag(expr)))
|
| 723 |
+
mrow.appendChild(x)
|
| 724 |
+
mrow.appendChild(y)
|
| 725 |
+
for term in terms:
|
| 726 |
+
mrow.appendChild(self.parenthesize(term, PRECEDENCE['Mul']))
|
| 727 |
+
if not term == terms[-1]:
|
| 728 |
+
y = self.dom.createElement('mo')
|
| 729 |
+
y.appendChild(self.dom.createTextNode(self.mathml_tag(expr)))
|
| 730 |
+
mrow.appendChild(y)
|
| 731 |
+
return mrow
|
| 732 |
+
mrow = self.dom.createElement('mrow')
|
| 733 |
+
if expr.could_extract_minus_sign():
|
| 734 |
+
x = self.dom.createElement('mo')
|
| 735 |
+
x.appendChild(self.dom.createTextNode('-'))
|
| 736 |
+
mrow.appendChild(x)
|
| 737 |
+
mrow = multiply(-expr, mrow)
|
| 738 |
+
else:
|
| 739 |
+
mrow = multiply(expr, mrow)
|
| 740 |
+
|
| 741 |
+
return mrow
|
| 742 |
+
|
| 743 |
+
def _print_Add(self, expr, order=None):
|
| 744 |
+
mrow = self.dom.createElement('mrow')
|
| 745 |
+
args = self._as_ordered_terms(expr, order=order)
|
| 746 |
+
mrow.appendChild(self._print(args[0]))
|
| 747 |
+
for arg in args[1:]:
|
| 748 |
+
if arg.could_extract_minus_sign():
|
| 749 |
+
# use minus
|
| 750 |
+
x = self.dom.createElement('mo')
|
| 751 |
+
x.appendChild(self.dom.createTextNode('-'))
|
| 752 |
+
y = self._print(-arg)
|
| 753 |
+
# invert expression since this is now minused
|
| 754 |
+
else:
|
| 755 |
+
x = self.dom.createElement('mo')
|
| 756 |
+
x.appendChild(self.dom.createTextNode('+'))
|
| 757 |
+
y = self._print(arg)
|
| 758 |
+
mrow.appendChild(x)
|
| 759 |
+
mrow.appendChild(y)
|
| 760 |
+
|
| 761 |
+
return mrow
|
| 762 |
+
|
| 763 |
+
def _print_MatrixBase(self, m):
|
| 764 |
+
table = self.dom.createElement('mtable')
|
| 765 |
+
for i in range(m.rows):
|
| 766 |
+
x = self.dom.createElement('mtr')
|
| 767 |
+
for j in range(m.cols):
|
| 768 |
+
y = self.dom.createElement('mtd')
|
| 769 |
+
y.appendChild(self._print(m[i, j]))
|
| 770 |
+
x.appendChild(y)
|
| 771 |
+
table.appendChild(x)
|
| 772 |
+
mat_delim = self._settings["mat_delim"]
|
| 773 |
+
if mat_delim == '':
|
| 774 |
+
return table
|
| 775 |
+
left = self.dom.createElement('mo')
|
| 776 |
+
right = self.dom.createElement('mo')
|
| 777 |
+
if mat_delim == "[":
|
| 778 |
+
left.appendChild(self.dom.createTextNode("["))
|
| 779 |
+
right.appendChild(self.dom.createTextNode("]"))
|
| 780 |
+
else:
|
| 781 |
+
left.appendChild(self.dom.createTextNode("("))
|
| 782 |
+
right.appendChild(self.dom.createTextNode(")"))
|
| 783 |
+
mrow = self.dom.createElement('mrow')
|
| 784 |
+
mrow.appendChild(left)
|
| 785 |
+
mrow.appendChild(table)
|
| 786 |
+
mrow.appendChild(right)
|
| 787 |
+
return mrow
|
| 788 |
+
|
| 789 |
+
def _get_printed_Rational(self, e, folded=None):
|
| 790 |
+
if e.p < 0:
|
| 791 |
+
p = -e.p
|
| 792 |
+
else:
|
| 793 |
+
p = e.p
|
| 794 |
+
x = self.dom.createElement('mfrac')
|
| 795 |
+
if folded or self._settings["fold_short_frac"]:
|
| 796 |
+
x.setAttribute('bevelled', 'true')
|
| 797 |
+
x.appendChild(self._print(p))
|
| 798 |
+
x.appendChild(self._print(e.q))
|
| 799 |
+
if e.p < 0:
|
| 800 |
+
mrow = self.dom.createElement('mrow')
|
| 801 |
+
mo = self.dom.createElement('mo')
|
| 802 |
+
mo.appendChild(self.dom.createTextNode('-'))
|
| 803 |
+
mrow.appendChild(mo)
|
| 804 |
+
mrow.appendChild(x)
|
| 805 |
+
return mrow
|
| 806 |
+
else:
|
| 807 |
+
return x
|
| 808 |
+
|
| 809 |
+
def _print_Rational(self, e):
|
| 810 |
+
if e.q == 1:
|
| 811 |
+
# don't divide
|
| 812 |
+
return self._print(e.p)
|
| 813 |
+
|
| 814 |
+
return self._get_printed_Rational(e, self._settings["fold_short_frac"])
|
| 815 |
+
|
| 816 |
+
def _print_Limit(self, e):
|
| 817 |
+
mrow = self.dom.createElement('mrow')
|
| 818 |
+
munder = self.dom.createElement('munder')
|
| 819 |
+
mi = self.dom.createElement('mi')
|
| 820 |
+
mi.appendChild(self.dom.createTextNode('lim'))
|
| 821 |
+
|
| 822 |
+
x = self.dom.createElement('mrow')
|
| 823 |
+
x_1 = self._print(e.args[1])
|
| 824 |
+
arrow = self.dom.createElement('mo')
|
| 825 |
+
arrow.appendChild(self.dom.createTextNode(self.mathml_tag(e)))
|
| 826 |
+
x_2 = self._print(e.args[2])
|
| 827 |
+
x.appendChild(x_1)
|
| 828 |
+
x.appendChild(arrow)
|
| 829 |
+
x.appendChild(x_2)
|
| 830 |
+
|
| 831 |
+
munder.appendChild(mi)
|
| 832 |
+
munder.appendChild(x)
|
| 833 |
+
mrow.appendChild(munder)
|
| 834 |
+
mrow.appendChild(self._print(e.args[0]))
|
| 835 |
+
|
| 836 |
+
return mrow
|
| 837 |
+
|
| 838 |
+
def _print_ImaginaryUnit(self, e):
|
| 839 |
+
x = self.dom.createElement('mi')
|
| 840 |
+
x.appendChild(self.dom.createTextNode('ⅈ'))
|
| 841 |
+
return x
|
| 842 |
+
|
| 843 |
+
def _print_GoldenRatio(self, e):
|
| 844 |
+
x = self.dom.createElement('mi')
|
| 845 |
+
x.appendChild(self.dom.createTextNode('Φ'))
|
| 846 |
+
return x
|
| 847 |
+
|
| 848 |
+
def _print_Exp1(self, e):
|
| 849 |
+
x = self.dom.createElement('mi')
|
| 850 |
+
x.appendChild(self.dom.createTextNode('ⅇ'))
|
| 851 |
+
return x
|
| 852 |
+
|
| 853 |
+
def _print_Pi(self, e):
|
| 854 |
+
x = self.dom.createElement('mi')
|
| 855 |
+
x.appendChild(self.dom.createTextNode('π'))
|
| 856 |
+
return x
|
| 857 |
+
|
| 858 |
+
def _print_Infinity(self, e):
|
| 859 |
+
x = self.dom.createElement('mi')
|
| 860 |
+
x.appendChild(self.dom.createTextNode('∞'))
|
| 861 |
+
return x
|
| 862 |
+
|
| 863 |
+
def _print_NegativeInfinity(self, e):
|
| 864 |
+
mrow = self.dom.createElement('mrow')
|
| 865 |
+
y = self.dom.createElement('mo')
|
| 866 |
+
y.appendChild(self.dom.createTextNode('-'))
|
| 867 |
+
x = self._print_Infinity(e)
|
| 868 |
+
mrow.appendChild(y)
|
| 869 |
+
mrow.appendChild(x)
|
| 870 |
+
return mrow
|
| 871 |
+
|
| 872 |
+
def _print_HBar(self, e):
|
| 873 |
+
x = self.dom.createElement('mi')
|
| 874 |
+
x.appendChild(self.dom.createTextNode('ℏ'))
|
| 875 |
+
return x
|
| 876 |
+
|
| 877 |
+
def _print_EulerGamma(self, e):
|
| 878 |
+
x = self.dom.createElement('mi')
|
| 879 |
+
x.appendChild(self.dom.createTextNode('γ'))
|
| 880 |
+
return x
|
| 881 |
+
|
| 882 |
+
def _print_TribonacciConstant(self, e):
|
| 883 |
+
x = self.dom.createElement('mi')
|
| 884 |
+
x.appendChild(self.dom.createTextNode('TribonacciConstant'))
|
| 885 |
+
return x
|
| 886 |
+
|
| 887 |
+
def _print_Dagger(self, e):
|
| 888 |
+
msup = self.dom.createElement('msup')
|
| 889 |
+
msup.appendChild(self._print(e.args[0]))
|
| 890 |
+
msup.appendChild(self.dom.createTextNode('†'))
|
| 891 |
+
return msup
|
| 892 |
+
|
| 893 |
+
def _print_Contains(self, e):
|
| 894 |
+
mrow = self.dom.createElement('mrow')
|
| 895 |
+
mrow.appendChild(self._print(e.args[0]))
|
| 896 |
+
mo = self.dom.createElement('mo')
|
| 897 |
+
mo.appendChild(self.dom.createTextNode('∈'))
|
| 898 |
+
mrow.appendChild(mo)
|
| 899 |
+
mrow.appendChild(self._print(e.args[1]))
|
| 900 |
+
return mrow
|
| 901 |
+
|
| 902 |
+
def _print_HilbertSpace(self, e):
|
| 903 |
+
x = self.dom.createElement('mi')
|
| 904 |
+
x.appendChild(self.dom.createTextNode('ℋ'))
|
| 905 |
+
return x
|
| 906 |
+
|
| 907 |
+
def _print_ComplexSpace(self, e):
|
| 908 |
+
msup = self.dom.createElement('msup')
|
| 909 |
+
msup.appendChild(self.dom.createTextNode('𝒞'))
|
| 910 |
+
msup.appendChild(self._print(e.args[0]))
|
| 911 |
+
return msup
|
| 912 |
+
|
| 913 |
+
def _print_FockSpace(self, e):
|
| 914 |
+
x = self.dom.createElement('mi')
|
| 915 |
+
x.appendChild(self.dom.createTextNode('ℱ'))
|
| 916 |
+
return x
|
| 917 |
+
|
| 918 |
+
|
| 919 |
+
def _print_Integral(self, expr):
|
| 920 |
+
intsymbols = {1: "∫", 2: "∬", 3: "∭"}
|
| 921 |
+
|
| 922 |
+
mrow = self.dom.createElement('mrow')
|
| 923 |
+
if len(expr.limits) <= 3 and all(len(lim) == 1 for lim in expr.limits):
|
| 924 |
+
# Only up to three-integral signs exists
|
| 925 |
+
mo = self.dom.createElement('mo')
|
| 926 |
+
mo.appendChild(self.dom.createTextNode(intsymbols[len(expr.limits)]))
|
| 927 |
+
mrow.appendChild(mo)
|
| 928 |
+
else:
|
| 929 |
+
# Either more than three or limits provided
|
| 930 |
+
for lim in reversed(expr.limits):
|
| 931 |
+
mo = self.dom.createElement('mo')
|
| 932 |
+
mo.appendChild(self.dom.createTextNode(intsymbols[1]))
|
| 933 |
+
if len(lim) == 1:
|
| 934 |
+
mrow.appendChild(mo)
|
| 935 |
+
if len(lim) == 2:
|
| 936 |
+
msup = self.dom.createElement('msup')
|
| 937 |
+
msup.appendChild(mo)
|
| 938 |
+
msup.appendChild(self._print(lim[1]))
|
| 939 |
+
mrow.appendChild(msup)
|
| 940 |
+
if len(lim) == 3:
|
| 941 |
+
msubsup = self.dom.createElement('msubsup')
|
| 942 |
+
msubsup.appendChild(mo)
|
| 943 |
+
msubsup.appendChild(self._print(lim[1]))
|
| 944 |
+
msubsup.appendChild(self._print(lim[2]))
|
| 945 |
+
mrow.appendChild(msubsup)
|
| 946 |
+
# print function
|
| 947 |
+
mrow.appendChild(self.parenthesize(expr.function, PRECEDENCE["Mul"],
|
| 948 |
+
strict=True))
|
| 949 |
+
# print integration variables
|
| 950 |
+
for lim in reversed(expr.limits):
|
| 951 |
+
d = self.dom.createElement('mo')
|
| 952 |
+
d.appendChild(self.dom.createTextNode('ⅆ'))
|
| 953 |
+
mrow.appendChild(d)
|
| 954 |
+
mrow.appendChild(self._print(lim[0]))
|
| 955 |
+
return mrow
|
| 956 |
+
|
| 957 |
+
def _print_Sum(self, e):
|
| 958 |
+
limits = list(e.limits)
|
| 959 |
+
subsup = self.dom.createElement('munderover')
|
| 960 |
+
low_elem = self._print(limits[0][1])
|
| 961 |
+
up_elem = self._print(limits[0][2])
|
| 962 |
+
summand = self.dom.createElement('mo')
|
| 963 |
+
summand.appendChild(self.dom.createTextNode(self.mathml_tag(e)))
|
| 964 |
+
|
| 965 |
+
low = self.dom.createElement('mrow')
|
| 966 |
+
var = self._print(limits[0][0])
|
| 967 |
+
equal = self.dom.createElement('mo')
|
| 968 |
+
equal.appendChild(self.dom.createTextNode('='))
|
| 969 |
+
low.appendChild(var)
|
| 970 |
+
low.appendChild(equal)
|
| 971 |
+
low.appendChild(low_elem)
|
| 972 |
+
|
| 973 |
+
subsup.appendChild(summand)
|
| 974 |
+
subsup.appendChild(low)
|
| 975 |
+
subsup.appendChild(up_elem)
|
| 976 |
+
|
| 977 |
+
mrow = self.dom.createElement('mrow')
|
| 978 |
+
mrow.appendChild(subsup)
|
| 979 |
+
mrow.appendChild(self.parenthesize(e.function, precedence_traditional(e)))
|
| 980 |
+
return mrow
|
| 981 |
+
|
| 982 |
+
def _print_Symbol(self, sym, style='plain'):
|
| 983 |
+
def join(items):
|
| 984 |
+
if len(items) > 1:
|
| 985 |
+
mrow = self.dom.createElement('mrow')
|
| 986 |
+
for i, item in enumerate(items):
|
| 987 |
+
if i > 0:
|
| 988 |
+
mo = self.dom.createElement('mo')
|
| 989 |
+
mo.appendChild(self.dom.createTextNode(" "))
|
| 990 |
+
mrow.appendChild(mo)
|
| 991 |
+
mi = self.dom.createElement('mi')
|
| 992 |
+
mi.appendChild(self.dom.createTextNode(item))
|
| 993 |
+
mrow.appendChild(mi)
|
| 994 |
+
return mrow
|
| 995 |
+
else:
|
| 996 |
+
mi = self.dom.createElement('mi')
|
| 997 |
+
mi.appendChild(self.dom.createTextNode(items[0]))
|
| 998 |
+
return mi
|
| 999 |
+
|
| 1000 |
+
# translate name, supers and subs to unicode characters
|
| 1001 |
+
def translate(s):
|
| 1002 |
+
if s in greek_unicode:
|
| 1003 |
+
return greek_unicode.get(s)
|
| 1004 |
+
else:
|
| 1005 |
+
return s
|
| 1006 |
+
|
| 1007 |
+
name, supers, subs = self._split_super_sub(sym.name)
|
| 1008 |
+
name = translate(name)
|
| 1009 |
+
supers = [translate(sup) for sup in supers]
|
| 1010 |
+
subs = [translate(sub) for sub in subs]
|
| 1011 |
+
|
| 1012 |
+
mname = self.dom.createElement('mi')
|
| 1013 |
+
mname.appendChild(self.dom.createTextNode(name))
|
| 1014 |
+
if len(supers) == 0:
|
| 1015 |
+
if len(subs) == 0:
|
| 1016 |
+
x = mname
|
| 1017 |
+
else:
|
| 1018 |
+
x = self.dom.createElement('msub')
|
| 1019 |
+
x.appendChild(mname)
|
| 1020 |
+
x.appendChild(join(subs))
|
| 1021 |
+
else:
|
| 1022 |
+
if len(subs) == 0:
|
| 1023 |
+
x = self.dom.createElement('msup')
|
| 1024 |
+
x.appendChild(mname)
|
| 1025 |
+
x.appendChild(join(supers))
|
| 1026 |
+
else:
|
| 1027 |
+
x = self.dom.createElement('msubsup')
|
| 1028 |
+
x.appendChild(mname)
|
| 1029 |
+
x.appendChild(join(subs))
|
| 1030 |
+
x.appendChild(join(supers))
|
| 1031 |
+
# Set bold font?
|
| 1032 |
+
if style == 'bold':
|
| 1033 |
+
x.setAttribute('mathvariant', 'bold')
|
| 1034 |
+
return x
|
| 1035 |
+
|
| 1036 |
+
def _print_MatrixSymbol(self, sym):
|
| 1037 |
+
return self._print_Symbol(sym,
|
| 1038 |
+
style=self._settings['mat_symbol_style'])
|
| 1039 |
+
|
| 1040 |
+
_print_RandomSymbol = _print_Symbol
|
| 1041 |
+
|
| 1042 |
+
def _print_conjugate(self, expr):
|
| 1043 |
+
enc = self.dom.createElement('menclose')
|
| 1044 |
+
enc.setAttribute('notation', 'top')
|
| 1045 |
+
enc.appendChild(self._print(expr.args[0]))
|
| 1046 |
+
return enc
|
| 1047 |
+
|
| 1048 |
+
def _print_operator_after(self, op, expr):
|
| 1049 |
+
row = self.dom.createElement('mrow')
|
| 1050 |
+
row.appendChild(self.parenthesize(expr, PRECEDENCE["Func"]))
|
| 1051 |
+
mo = self.dom.createElement('mo')
|
| 1052 |
+
mo.appendChild(self.dom.createTextNode(op))
|
| 1053 |
+
row.appendChild(mo)
|
| 1054 |
+
return row
|
| 1055 |
+
|
| 1056 |
+
def _print_factorial(self, expr):
|
| 1057 |
+
return self._print_operator_after('!', expr.args[0])
|
| 1058 |
+
|
| 1059 |
+
def _print_factorial2(self, expr):
|
| 1060 |
+
return self._print_operator_after('!!', expr.args[0])
|
| 1061 |
+
|
| 1062 |
+
def _print_binomial(self, expr):
|
| 1063 |
+
frac = self.dom.createElement('mfrac')
|
| 1064 |
+
frac.setAttribute('linethickness', '0')
|
| 1065 |
+
frac.appendChild(self._print(expr.args[0]))
|
| 1066 |
+
frac.appendChild(self._print(expr.args[1]))
|
| 1067 |
+
brac = self.dom.createElement('mrow')
|
| 1068 |
+
brac.appendChild(self._l_paren())
|
| 1069 |
+
brac.appendChild(frac)
|
| 1070 |
+
brac.appendChild(self._r_paren())
|
| 1071 |
+
return brac
|
| 1072 |
+
|
| 1073 |
+
def _print_Pow(self, e):
|
| 1074 |
+
# Here we use root instead of power if the exponent is the
|
| 1075 |
+
# reciprocal of an integer
|
| 1076 |
+
if (e.exp.is_Rational and abs(e.exp.p) == 1 and e.exp.q != 1 and
|
| 1077 |
+
self._settings['root_notation']):
|
| 1078 |
+
if e.exp.q == 2:
|
| 1079 |
+
x = self.dom.createElement('msqrt')
|
| 1080 |
+
x.appendChild(self._print(e.base))
|
| 1081 |
+
if e.exp.q != 2:
|
| 1082 |
+
x = self.dom.createElement('mroot')
|
| 1083 |
+
x.appendChild(self._print(e.base))
|
| 1084 |
+
x.appendChild(self._print(e.exp.q))
|
| 1085 |
+
if e.exp.p == -1:
|
| 1086 |
+
frac = self.dom.createElement('mfrac')
|
| 1087 |
+
frac.appendChild(self._print(1))
|
| 1088 |
+
frac.appendChild(x)
|
| 1089 |
+
return frac
|
| 1090 |
+
else:
|
| 1091 |
+
return x
|
| 1092 |
+
|
| 1093 |
+
if e.exp.is_Rational and e.exp.q != 1:
|
| 1094 |
+
if e.exp.is_negative:
|
| 1095 |
+
top = self.dom.createElement('mfrac')
|
| 1096 |
+
top.appendChild(self._print(1))
|
| 1097 |
+
x = self.dom.createElement('msup')
|
| 1098 |
+
x.appendChild(self.parenthesize(e.base, PRECEDENCE['Pow']))
|
| 1099 |
+
x.appendChild(self._get_printed_Rational(-e.exp,
|
| 1100 |
+
self._settings['fold_frac_powers']))
|
| 1101 |
+
top.appendChild(x)
|
| 1102 |
+
return top
|
| 1103 |
+
else:
|
| 1104 |
+
x = self.dom.createElement('msup')
|
| 1105 |
+
x.appendChild(self.parenthesize(e.base, PRECEDENCE['Pow']))
|
| 1106 |
+
x.appendChild(self._get_printed_Rational(e.exp,
|
| 1107 |
+
self._settings['fold_frac_powers']))
|
| 1108 |
+
return x
|
| 1109 |
+
|
| 1110 |
+
if e.exp.is_negative:
|
| 1111 |
+
top = self.dom.createElement('mfrac')
|
| 1112 |
+
top.appendChild(self._print(1))
|
| 1113 |
+
if e.exp == -1:
|
| 1114 |
+
top.appendChild(self._print(e.base))
|
| 1115 |
+
else:
|
| 1116 |
+
x = self.dom.createElement('msup')
|
| 1117 |
+
x.appendChild(self.parenthesize(e.base, PRECEDENCE['Pow']))
|
| 1118 |
+
x.appendChild(self._print(-e.exp))
|
| 1119 |
+
top.appendChild(x)
|
| 1120 |
+
return top
|
| 1121 |
+
|
| 1122 |
+
x = self.dom.createElement('msup')
|
| 1123 |
+
x.appendChild(self.parenthesize(e.base, PRECEDENCE['Pow']))
|
| 1124 |
+
x.appendChild(self._print(e.exp))
|
| 1125 |
+
return x
|
| 1126 |
+
|
| 1127 |
+
def _print_Number(self, e):
|
| 1128 |
+
x = self.dom.createElement(self.mathml_tag(e))
|
| 1129 |
+
x.appendChild(self.dom.createTextNode(str(e)))
|
| 1130 |
+
return x
|
| 1131 |
+
|
| 1132 |
+
def _print_AccumulationBounds(self, i):
|
| 1133 |
+
left = self.dom.createElement('mo')
|
| 1134 |
+
left.appendChild(self.dom.createTextNode('\u27e8'))
|
| 1135 |
+
right = self.dom.createElement('mo')
|
| 1136 |
+
right.appendChild(self.dom.createTextNode('\u27e9'))
|
| 1137 |
+
brac = self.dom.createElement('mrow')
|
| 1138 |
+
brac.appendChild(left)
|
| 1139 |
+
brac.appendChild(self._print(i.min))
|
| 1140 |
+
brac.appendChild(self._comma())
|
| 1141 |
+
brac.appendChild(self._print(i.max))
|
| 1142 |
+
brac.appendChild(right)
|
| 1143 |
+
return brac
|
| 1144 |
+
|
| 1145 |
+
def _print_Derivative(self, e):
|
| 1146 |
+
|
| 1147 |
+
if requires_partial(e.expr):
|
| 1148 |
+
d = '∂'
|
| 1149 |
+
else:
|
| 1150 |
+
d = self.mathml_tag(e)
|
| 1151 |
+
|
| 1152 |
+
# Determine denominator
|
| 1153 |
+
m = self.dom.createElement('mrow')
|
| 1154 |
+
dim = 0 # Total diff dimension, for numerator
|
| 1155 |
+
for sym, num in reversed(e.variable_count):
|
| 1156 |
+
dim += num
|
| 1157 |
+
if num >= 2:
|
| 1158 |
+
x = self.dom.createElement('msup')
|
| 1159 |
+
xx = self.dom.createElement('mo')
|
| 1160 |
+
xx.appendChild(self.dom.createTextNode(d))
|
| 1161 |
+
x.appendChild(xx)
|
| 1162 |
+
x.appendChild(self._print(num))
|
| 1163 |
+
else:
|
| 1164 |
+
x = self.dom.createElement('mo')
|
| 1165 |
+
x.appendChild(self.dom.createTextNode(d))
|
| 1166 |
+
m.appendChild(x)
|
| 1167 |
+
y = self._print(sym)
|
| 1168 |
+
m.appendChild(y)
|
| 1169 |
+
|
| 1170 |
+
mnum = self.dom.createElement('mrow')
|
| 1171 |
+
if dim >= 2:
|
| 1172 |
+
x = self.dom.createElement('msup')
|
| 1173 |
+
xx = self.dom.createElement('mo')
|
| 1174 |
+
xx.appendChild(self.dom.createTextNode(d))
|
| 1175 |
+
x.appendChild(xx)
|
| 1176 |
+
x.appendChild(self._print(dim))
|
| 1177 |
+
else:
|
| 1178 |
+
x = self.dom.createElement('mo')
|
| 1179 |
+
x.appendChild(self.dom.createTextNode(d))
|
| 1180 |
+
|
| 1181 |
+
mnum.appendChild(x)
|
| 1182 |
+
mrow = self.dom.createElement('mrow')
|
| 1183 |
+
frac = self.dom.createElement('mfrac')
|
| 1184 |
+
frac.appendChild(mnum)
|
| 1185 |
+
frac.appendChild(m)
|
| 1186 |
+
mrow.appendChild(frac)
|
| 1187 |
+
|
| 1188 |
+
# Print function
|
| 1189 |
+
mrow.appendChild(self._print(e.expr))
|
| 1190 |
+
|
| 1191 |
+
return mrow
|
| 1192 |
+
|
| 1193 |
+
def _print_Function(self, e):
|
| 1194 |
+
x = self.dom.createElement('mi')
|
| 1195 |
+
if self.mathml_tag(e) == 'log' and self._settings["ln_notation"]:
|
| 1196 |
+
x.appendChild(self.dom.createTextNode('ln'))
|
| 1197 |
+
else:
|
| 1198 |
+
x.appendChild(self.dom.createTextNode(self.mathml_tag(e)))
|
| 1199 |
+
mrow = self.dom.createElement('mrow')
|
| 1200 |
+
mrow.appendChild(x)
|
| 1201 |
+
mrow.appendChild(self._paren_comma_separated(*e.args))
|
| 1202 |
+
return mrow
|
| 1203 |
+
|
| 1204 |
+
def _print_Float(self, expr):
|
| 1205 |
+
# Based off of that in StrPrinter
|
| 1206 |
+
dps = prec_to_dps(expr._prec)
|
| 1207 |
+
str_real = mlib_to_str(expr._mpf_, dps, strip_zeros=True)
|
| 1208 |
+
|
| 1209 |
+
# Must always have a mul symbol (as 2.5 10^{20} just looks odd)
|
| 1210 |
+
# thus we use the number separator
|
| 1211 |
+
separator = self._settings['mul_symbol_mathml_numbers']
|
| 1212 |
+
mrow = self.dom.createElement('mrow')
|
| 1213 |
+
if 'e' in str_real:
|
| 1214 |
+
(mant, exp) = str_real.split('e')
|
| 1215 |
+
|
| 1216 |
+
if exp[0] == '+':
|
| 1217 |
+
exp = exp[1:]
|
| 1218 |
+
|
| 1219 |
+
mn = self.dom.createElement('mn')
|
| 1220 |
+
mn.appendChild(self.dom.createTextNode(mant))
|
| 1221 |
+
mrow.appendChild(mn)
|
| 1222 |
+
mo = self.dom.createElement('mo')
|
| 1223 |
+
mo.appendChild(self.dom.createTextNode(separator))
|
| 1224 |
+
mrow.appendChild(mo)
|
| 1225 |
+
msup = self.dom.createElement('msup')
|
| 1226 |
+
mn = self.dom.createElement('mn')
|
| 1227 |
+
mn.appendChild(self.dom.createTextNode("10"))
|
| 1228 |
+
msup.appendChild(mn)
|
| 1229 |
+
mn = self.dom.createElement('mn')
|
| 1230 |
+
mn.appendChild(self.dom.createTextNode(exp))
|
| 1231 |
+
msup.appendChild(mn)
|
| 1232 |
+
mrow.appendChild(msup)
|
| 1233 |
+
return mrow
|
| 1234 |
+
elif str_real == "+inf":
|
| 1235 |
+
return self._print_Infinity(None)
|
| 1236 |
+
elif str_real == "-inf":
|
| 1237 |
+
return self._print_NegativeInfinity(None)
|
| 1238 |
+
else:
|
| 1239 |
+
mn = self.dom.createElement('mn')
|
| 1240 |
+
mn.appendChild(self.dom.createTextNode(str_real))
|
| 1241 |
+
return mn
|
| 1242 |
+
|
| 1243 |
+
def _print_polylog(self, expr):
|
| 1244 |
+
mrow = self.dom.createElement('mrow')
|
| 1245 |
+
m = self.dom.createElement('msub')
|
| 1246 |
+
|
| 1247 |
+
mi = self.dom.createElement('mi')
|
| 1248 |
+
mi.appendChild(self.dom.createTextNode('Li'))
|
| 1249 |
+
m.appendChild(mi)
|
| 1250 |
+
m.appendChild(self._print(expr.args[0]))
|
| 1251 |
+
mrow.appendChild(m)
|
| 1252 |
+
brac = self.dom.createElement('mrow')
|
| 1253 |
+
brac.appendChild(self._l_paren())
|
| 1254 |
+
brac.appendChild(self._print(expr.args[1]))
|
| 1255 |
+
brac.appendChild(self._r_paren())
|
| 1256 |
+
mrow.appendChild(brac)
|
| 1257 |
+
return mrow
|
| 1258 |
+
|
| 1259 |
+
def _print_Basic(self, e):
|
| 1260 |
+
mrow = self.dom.createElement('mrow')
|
| 1261 |
+
mi = self.dom.createElement('mi')
|
| 1262 |
+
mi.appendChild(self.dom.createTextNode(self.mathml_tag(e)))
|
| 1263 |
+
mrow.appendChild(mi)
|
| 1264 |
+
mrow.appendChild(self._paren_comma_separated(*e.args))
|
| 1265 |
+
return mrow
|
| 1266 |
+
|
| 1267 |
+
def _print_Tuple(self, e):
|
| 1268 |
+
return self._paren_comma_separated(*e.args)
|
| 1269 |
+
|
| 1270 |
+
def _print_Interval(self, i):
|
| 1271 |
+
right = self.dom.createElement('mo')
|
| 1272 |
+
if i.right_open:
|
| 1273 |
+
right.appendChild(self.dom.createTextNode(')'))
|
| 1274 |
+
else:
|
| 1275 |
+
right.appendChild(self.dom.createTextNode(']'))
|
| 1276 |
+
left = self.dom.createElement('mo')
|
| 1277 |
+
if i.left_open:
|
| 1278 |
+
left.appendChild(self.dom.createTextNode('('))
|
| 1279 |
+
else:
|
| 1280 |
+
left.appendChild(self.dom.createTextNode('['))
|
| 1281 |
+
mrow = self.dom.createElement('mrow')
|
| 1282 |
+
mrow.appendChild(left)
|
| 1283 |
+
mrow.appendChild(self._print(i.start))
|
| 1284 |
+
mrow.appendChild(self._comma())
|
| 1285 |
+
mrow.appendChild(self._print(i.end))
|
| 1286 |
+
mrow.appendChild(right)
|
| 1287 |
+
return mrow
|
| 1288 |
+
|
| 1289 |
+
def _print_Abs(self, expr, exp=None):
|
| 1290 |
+
mrow = self.dom.createElement('mrow')
|
| 1291 |
+
mrow.appendChild(self._bar())
|
| 1292 |
+
mrow.appendChild(self._print(expr.args[0]))
|
| 1293 |
+
mrow.appendChild(self._bar())
|
| 1294 |
+
return mrow
|
| 1295 |
+
|
| 1296 |
+
_print_Determinant = _print_Abs
|
| 1297 |
+
|
| 1298 |
+
def _print_re_im(self, c, expr):
|
| 1299 |
+
brac = self.dom.createElement('mrow')
|
| 1300 |
+
brac.appendChild(self._l_paren())
|
| 1301 |
+
brac.appendChild(self._print(expr))
|
| 1302 |
+
brac.appendChild(self._r_paren())
|
| 1303 |
+
mi = self.dom.createElement('mi')
|
| 1304 |
+
mi.appendChild(self.dom.createTextNode(c))
|
| 1305 |
+
mrow = self.dom.createElement('mrow')
|
| 1306 |
+
mrow.appendChild(mi)
|
| 1307 |
+
mrow.appendChild(brac)
|
| 1308 |
+
return mrow
|
| 1309 |
+
|
| 1310 |
+
def _print_re(self, expr, exp=None):
|
| 1311 |
+
return self._print_re_im('\u211C', expr.args[0])
|
| 1312 |
+
|
| 1313 |
+
def _print_im(self, expr, exp=None):
|
| 1314 |
+
return self._print_re_im('\u2111', expr.args[0])
|
| 1315 |
+
|
| 1316 |
+
def _print_AssocOp(self, e):
|
| 1317 |
+
mrow = self.dom.createElement('mrow')
|
| 1318 |
+
mi = self.dom.createElement('mi')
|
| 1319 |
+
mi.appendChild(self.dom.createTextNode(self.mathml_tag(e)))
|
| 1320 |
+
mrow.appendChild(mi)
|
| 1321 |
+
for arg in e.args:
|
| 1322 |
+
mrow.appendChild(self._print(arg))
|
| 1323 |
+
return mrow
|
| 1324 |
+
|
| 1325 |
+
def _print_SetOp(self, expr, symbol, prec):
|
| 1326 |
+
mrow = self.dom.createElement('mrow')
|
| 1327 |
+
mrow.appendChild(self.parenthesize(expr.args[0], prec))
|
| 1328 |
+
for arg in expr.args[1:]:
|
| 1329 |
+
x = self.dom.createElement('mo')
|
| 1330 |
+
x.appendChild(self.dom.createTextNode(symbol))
|
| 1331 |
+
y = self.parenthesize(arg, prec)
|
| 1332 |
+
mrow.appendChild(x)
|
| 1333 |
+
mrow.appendChild(y)
|
| 1334 |
+
return mrow
|
| 1335 |
+
|
| 1336 |
+
def _print_Union(self, expr):
|
| 1337 |
+
prec = PRECEDENCE_TRADITIONAL['Union']
|
| 1338 |
+
return self._print_SetOp(expr, '∪', prec)
|
| 1339 |
+
|
| 1340 |
+
def _print_Intersection(self, expr):
|
| 1341 |
+
prec = PRECEDENCE_TRADITIONAL['Intersection']
|
| 1342 |
+
return self._print_SetOp(expr, '∩', prec)
|
| 1343 |
+
|
| 1344 |
+
def _print_Complement(self, expr):
|
| 1345 |
+
prec = PRECEDENCE_TRADITIONAL['Complement']
|
| 1346 |
+
return self._print_SetOp(expr, '∖', prec)
|
| 1347 |
+
|
| 1348 |
+
def _print_SymmetricDifference(self, expr):
|
| 1349 |
+
prec = PRECEDENCE_TRADITIONAL['SymmetricDifference']
|
| 1350 |
+
return self._print_SetOp(expr, '∆', prec)
|
| 1351 |
+
|
| 1352 |
+
def _print_ProductSet(self, expr):
|
| 1353 |
+
prec = PRECEDENCE_TRADITIONAL['ProductSet']
|
| 1354 |
+
return self._print_SetOp(expr, '×', prec)
|
| 1355 |
+
|
| 1356 |
+
def _print_FiniteSet(self, s):
|
| 1357 |
+
return self._print_set(s.args)
|
| 1358 |
+
|
| 1359 |
+
def _print_set(self, s):
|
| 1360 |
+
items = sorted(s, key=default_sort_key)
|
| 1361 |
+
brac = self.dom.createElement('mrow')
|
| 1362 |
+
brac.appendChild(self._l_brace())
|
| 1363 |
+
for i, item in enumerate(items):
|
| 1364 |
+
if i:
|
| 1365 |
+
brac.appendChild(self._comma())
|
| 1366 |
+
brac.appendChild(self._print(item))
|
| 1367 |
+
brac.appendChild(self._r_brace())
|
| 1368 |
+
return brac
|
| 1369 |
+
|
| 1370 |
+
_print_frozenset = _print_set
|
| 1371 |
+
|
| 1372 |
+
def _print_LogOp(self, args, symbol):
|
| 1373 |
+
mrow = self.dom.createElement('mrow')
|
| 1374 |
+
if args[0].is_Boolean and not args[0].is_Not:
|
| 1375 |
+
brac = self.dom.createElement('mrow')
|
| 1376 |
+
brac.appendChild(self._l_paren())
|
| 1377 |
+
brac.appendChild(self._print(args[0]))
|
| 1378 |
+
brac.appendChild(self._r_paren())
|
| 1379 |
+
mrow.appendChild(brac)
|
| 1380 |
+
else:
|
| 1381 |
+
mrow.appendChild(self._print(args[0]))
|
| 1382 |
+
for arg in args[1:]:
|
| 1383 |
+
x = self.dom.createElement('mo')
|
| 1384 |
+
x.appendChild(self.dom.createTextNode(symbol))
|
| 1385 |
+
if arg.is_Boolean and not arg.is_Not:
|
| 1386 |
+
y = self.dom.createElement('mrow')
|
| 1387 |
+
y.appendChild(self._l_paren())
|
| 1388 |
+
y.appendChild(self._print(arg))
|
| 1389 |
+
y.appendChild(self._r_paren())
|
| 1390 |
+
else:
|
| 1391 |
+
y = self._print(arg)
|
| 1392 |
+
mrow.appendChild(x)
|
| 1393 |
+
mrow.appendChild(y)
|
| 1394 |
+
return mrow
|
| 1395 |
+
|
| 1396 |
+
def _print_BasisDependent(self, expr):
|
| 1397 |
+
from sympy.vector import Vector
|
| 1398 |
+
|
| 1399 |
+
if expr == expr.zero:
|
| 1400 |
+
# Not clear if this is ever called
|
| 1401 |
+
return self._print(expr.zero)
|
| 1402 |
+
if isinstance(expr, Vector):
|
| 1403 |
+
items = expr.separate().items()
|
| 1404 |
+
else:
|
| 1405 |
+
items = [(0, expr)]
|
| 1406 |
+
|
| 1407 |
+
mrow = self.dom.createElement('mrow')
|
| 1408 |
+
for system, vect in items:
|
| 1409 |
+
inneritems = list(vect.components.items())
|
| 1410 |
+
inneritems.sort(key = lambda x:x[0].__str__())
|
| 1411 |
+
for i, (k, v) in enumerate(inneritems):
|
| 1412 |
+
if v == 1:
|
| 1413 |
+
if i: # No + for first item
|
| 1414 |
+
mo = self.dom.createElement('mo')
|
| 1415 |
+
mo.appendChild(self.dom.createTextNode('+'))
|
| 1416 |
+
mrow.appendChild(mo)
|
| 1417 |
+
mrow.appendChild(self._print(k))
|
| 1418 |
+
elif v == -1:
|
| 1419 |
+
mo = self.dom.createElement('mo')
|
| 1420 |
+
mo.appendChild(self.dom.createTextNode('-'))
|
| 1421 |
+
mrow.appendChild(mo)
|
| 1422 |
+
mrow.appendChild(self._print(k))
|
| 1423 |
+
else:
|
| 1424 |
+
if i: # No + for first item
|
| 1425 |
+
mo = self.dom.createElement('mo')
|
| 1426 |
+
mo.appendChild(self.dom.createTextNode('+'))
|
| 1427 |
+
mrow.appendChild(mo)
|
| 1428 |
+
mbrac = self.dom.createElement('mrow')
|
| 1429 |
+
mbrac.appendChild(self._l_paren())
|
| 1430 |
+
mbrac.appendChild(self._print(v))
|
| 1431 |
+
mbrac.appendChild(self._r_paren())
|
| 1432 |
+
mrow.appendChild(mbrac)
|
| 1433 |
+
mo = self.dom.createElement('mo')
|
| 1434 |
+
mo.appendChild(self.dom.createTextNode('⁢'))
|
| 1435 |
+
mrow.appendChild(mo)
|
| 1436 |
+
mrow.appendChild(self._print(k))
|
| 1437 |
+
return mrow
|
| 1438 |
+
|
| 1439 |
+
|
| 1440 |
+
def _print_And(self, expr):
|
| 1441 |
+
args = sorted(expr.args, key=default_sort_key)
|
| 1442 |
+
return self._print_LogOp(args, '∧')
|
| 1443 |
+
|
| 1444 |
+
def _print_Or(self, expr):
|
| 1445 |
+
args = sorted(expr.args, key=default_sort_key)
|
| 1446 |
+
return self._print_LogOp(args, '∨')
|
| 1447 |
+
|
| 1448 |
+
def _print_Xor(self, expr):
|
| 1449 |
+
args = sorted(expr.args, key=default_sort_key)
|
| 1450 |
+
return self._print_LogOp(args, '⊻')
|
| 1451 |
+
|
| 1452 |
+
def _print_Implies(self, expr):
|
| 1453 |
+
return self._print_LogOp(expr.args, '⇒')
|
| 1454 |
+
|
| 1455 |
+
def _print_Equivalent(self, expr):
|
| 1456 |
+
args = sorted(expr.args, key=default_sort_key)
|
| 1457 |
+
return self._print_LogOp(args, '⇔')
|
| 1458 |
+
|
| 1459 |
+
def _print_Not(self, e):
|
| 1460 |
+
mrow = self.dom.createElement('mrow')
|
| 1461 |
+
mo = self.dom.createElement('mo')
|
| 1462 |
+
mo.appendChild(self.dom.createTextNode('¬'))
|
| 1463 |
+
mrow.appendChild(mo)
|
| 1464 |
+
if (e.args[0].is_Boolean):
|
| 1465 |
+
x = self.dom.createElement('mrow')
|
| 1466 |
+
x.appendChild(self._l_paren())
|
| 1467 |
+
x.appendChild(self._print(e.args[0]))
|
| 1468 |
+
x.appendChild(self._r_paren())
|
| 1469 |
+
else:
|
| 1470 |
+
x = self._print(e.args[0])
|
| 1471 |
+
mrow.appendChild(x)
|
| 1472 |
+
return mrow
|
| 1473 |
+
|
| 1474 |
+
def _print_bool(self, e):
|
| 1475 |
+
mi = self.dom.createElement('mi')
|
| 1476 |
+
mi.appendChild(self.dom.createTextNode(self.mathml_tag(e)))
|
| 1477 |
+
return mi
|
| 1478 |
+
|
| 1479 |
+
_print_BooleanTrue = _print_bool
|
| 1480 |
+
_print_BooleanFalse = _print_bool
|
| 1481 |
+
|
| 1482 |
+
def _print_NoneType(self, e):
|
| 1483 |
+
mi = self.dom.createElement('mi')
|
| 1484 |
+
mi.appendChild(self.dom.createTextNode(self.mathml_tag(e)))
|
| 1485 |
+
return mi
|
| 1486 |
+
|
| 1487 |
+
def _print_Range(self, s):
|
| 1488 |
+
dots = "\u2026"
|
| 1489 |
+
if s.start.is_infinite and s.stop.is_infinite:
|
| 1490 |
+
if s.step.is_positive:
|
| 1491 |
+
printset = dots, -1, 0, 1, dots
|
| 1492 |
+
else:
|
| 1493 |
+
printset = dots, 1, 0, -1, dots
|
| 1494 |
+
elif s.start.is_infinite:
|
| 1495 |
+
printset = dots, s[-1] - s.step, s[-1]
|
| 1496 |
+
elif s.stop.is_infinite:
|
| 1497 |
+
it = iter(s)
|
| 1498 |
+
printset = next(it), next(it), dots
|
| 1499 |
+
elif len(s) > 4:
|
| 1500 |
+
it = iter(s)
|
| 1501 |
+
printset = next(it), next(it), dots, s[-1]
|
| 1502 |
+
else:
|
| 1503 |
+
printset = tuple(s)
|
| 1504 |
+
brac = self.dom.createElement('mrow')
|
| 1505 |
+
brac.appendChild(self._l_brace())
|
| 1506 |
+
for i, el in enumerate(printset):
|
| 1507 |
+
if i:
|
| 1508 |
+
brac.appendChild(self._comma())
|
| 1509 |
+
if el == dots:
|
| 1510 |
+
mi = self.dom.createElement('mi')
|
| 1511 |
+
mi.appendChild(self.dom.createTextNode(dots))
|
| 1512 |
+
brac.appendChild(mi)
|
| 1513 |
+
else:
|
| 1514 |
+
brac.appendChild(self._print(el))
|
| 1515 |
+
brac.appendChild(self._r_brace())
|
| 1516 |
+
return brac
|
| 1517 |
+
|
| 1518 |
+
def _hprint_variadic_function(self, expr):
|
| 1519 |
+
args = sorted(expr.args, key=default_sort_key)
|
| 1520 |
+
mrow = self.dom.createElement('mrow')
|
| 1521 |
+
mo = self.dom.createElement('mo')
|
| 1522 |
+
mo.appendChild(self.dom.createTextNode((str(expr.func)).lower()))
|
| 1523 |
+
mrow.appendChild(mo)
|
| 1524 |
+
mrow.appendChild(self._paren_comma_separated(*args))
|
| 1525 |
+
return mrow
|
| 1526 |
+
|
| 1527 |
+
_print_Min = _print_Max = _hprint_variadic_function
|
| 1528 |
+
|
| 1529 |
+
def _print_exp(self, expr):
|
| 1530 |
+
msup = self.dom.createElement('msup')
|
| 1531 |
+
msup.appendChild(self._print_Exp1(None))
|
| 1532 |
+
msup.appendChild(self._print(expr.args[0]))
|
| 1533 |
+
return msup
|
| 1534 |
+
|
| 1535 |
+
def _print_Relational(self, e):
|
| 1536 |
+
mrow = self.dom.createElement('mrow')
|
| 1537 |
+
mrow.appendChild(self._print(e.lhs))
|
| 1538 |
+
x = self.dom.createElement('mo')
|
| 1539 |
+
x.appendChild(self.dom.createTextNode(self.mathml_tag(e)))
|
| 1540 |
+
mrow.appendChild(x)
|
| 1541 |
+
mrow.appendChild(self._print(e.rhs))
|
| 1542 |
+
return mrow
|
| 1543 |
+
|
| 1544 |
+
def _print_int(self, p):
|
| 1545 |
+
dom_element = self.dom.createElement(self.mathml_tag(p))
|
| 1546 |
+
dom_element.appendChild(self.dom.createTextNode(str(p)))
|
| 1547 |
+
return dom_element
|
| 1548 |
+
|
| 1549 |
+
def _print_BaseScalar(self, e):
|
| 1550 |
+
msub = self.dom.createElement('msub')
|
| 1551 |
+
index, system = e._id
|
| 1552 |
+
mi = self.dom.createElement('mi')
|
| 1553 |
+
mi.setAttribute('mathvariant', 'bold')
|
| 1554 |
+
mi.appendChild(self.dom.createTextNode(system._variable_names[index]))
|
| 1555 |
+
msub.appendChild(mi)
|
| 1556 |
+
mi = self.dom.createElement('mi')
|
| 1557 |
+
mi.setAttribute('mathvariant', 'bold')
|
| 1558 |
+
mi.appendChild(self.dom.createTextNode(system._name))
|
| 1559 |
+
msub.appendChild(mi)
|
| 1560 |
+
return msub
|
| 1561 |
+
|
| 1562 |
+
def _print_BaseVector(self, e):
|
| 1563 |
+
msub = self.dom.createElement('msub')
|
| 1564 |
+
index, system = e._id
|
| 1565 |
+
mover = self.dom.createElement('mover')
|
| 1566 |
+
mi = self.dom.createElement('mi')
|
| 1567 |
+
mi.setAttribute('mathvariant', 'bold')
|
| 1568 |
+
mi.appendChild(self.dom.createTextNode(system._vector_names[index]))
|
| 1569 |
+
mover.appendChild(mi)
|
| 1570 |
+
mo = self.dom.createElement('mo')
|
| 1571 |
+
mo.appendChild(self.dom.createTextNode('^'))
|
| 1572 |
+
mover.appendChild(mo)
|
| 1573 |
+
msub.appendChild(mover)
|
| 1574 |
+
mi = self.dom.createElement('mi')
|
| 1575 |
+
mi.setAttribute('mathvariant', 'bold')
|
| 1576 |
+
mi.appendChild(self.dom.createTextNode(system._name))
|
| 1577 |
+
msub.appendChild(mi)
|
| 1578 |
+
return msub
|
| 1579 |
+
|
| 1580 |
+
def _print_VectorZero(self, e):
|
| 1581 |
+
mover = self.dom.createElement('mover')
|
| 1582 |
+
mi = self.dom.createElement('mi')
|
| 1583 |
+
mi.setAttribute('mathvariant', 'bold')
|
| 1584 |
+
mi.appendChild(self.dom.createTextNode("0"))
|
| 1585 |
+
mover.appendChild(mi)
|
| 1586 |
+
mo = self.dom.createElement('mo')
|
| 1587 |
+
mo.appendChild(self.dom.createTextNode('^'))
|
| 1588 |
+
mover.appendChild(mo)
|
| 1589 |
+
return mover
|
| 1590 |
+
|
| 1591 |
+
def _print_Cross(self, expr):
|
| 1592 |
+
mrow = self.dom.createElement('mrow')
|
| 1593 |
+
vec1 = expr._expr1
|
| 1594 |
+
vec2 = expr._expr2
|
| 1595 |
+
mrow.appendChild(self.parenthesize(vec1, PRECEDENCE['Mul']))
|
| 1596 |
+
mo = self.dom.createElement('mo')
|
| 1597 |
+
mo.appendChild(self.dom.createTextNode('×'))
|
| 1598 |
+
mrow.appendChild(mo)
|
| 1599 |
+
mrow.appendChild(self.parenthesize(vec2, PRECEDENCE['Mul']))
|
| 1600 |
+
return mrow
|
| 1601 |
+
|
| 1602 |
+
def _print_Curl(self, expr):
|
| 1603 |
+
mrow = self.dom.createElement('mrow')
|
| 1604 |
+
mo = self.dom.createElement('mo')
|
| 1605 |
+
mo.appendChild(self.dom.createTextNode('∇'))
|
| 1606 |
+
mrow.appendChild(mo)
|
| 1607 |
+
mo = self.dom.createElement('mo')
|
| 1608 |
+
mo.appendChild(self.dom.createTextNode('×'))
|
| 1609 |
+
mrow.appendChild(mo)
|
| 1610 |
+
mrow.appendChild(self.parenthesize(expr._expr, PRECEDENCE['Mul']))
|
| 1611 |
+
return mrow
|
| 1612 |
+
|
| 1613 |
+
def _print_Divergence(self, expr):
|
| 1614 |
+
mrow = self.dom.createElement('mrow')
|
| 1615 |
+
mo = self.dom.createElement('mo')
|
| 1616 |
+
mo.appendChild(self.dom.createTextNode('∇'))
|
| 1617 |
+
mrow.appendChild(mo)
|
| 1618 |
+
mo = self.dom.createElement('mo')
|
| 1619 |
+
mo.appendChild(self.dom.createTextNode('·'))
|
| 1620 |
+
mrow.appendChild(mo)
|
| 1621 |
+
mrow.appendChild(self.parenthesize(expr._expr, PRECEDENCE['Mul']))
|
| 1622 |
+
return mrow
|
| 1623 |
+
|
| 1624 |
+
def _print_Dot(self, expr):
|
| 1625 |
+
mrow = self.dom.createElement('mrow')
|
| 1626 |
+
vec1 = expr._expr1
|
| 1627 |
+
vec2 = expr._expr2
|
| 1628 |
+
mrow.appendChild(self.parenthesize(vec1, PRECEDENCE['Mul']))
|
| 1629 |
+
mo = self.dom.createElement('mo')
|
| 1630 |
+
mo.appendChild(self.dom.createTextNode('·'))
|
| 1631 |
+
mrow.appendChild(mo)
|
| 1632 |
+
mrow.appendChild(self.parenthesize(vec2, PRECEDENCE['Mul']))
|
| 1633 |
+
return mrow
|
| 1634 |
+
|
| 1635 |
+
def _print_Gradient(self, expr):
|
| 1636 |
+
mrow = self.dom.createElement('mrow')
|
| 1637 |
+
mo = self.dom.createElement('mo')
|
| 1638 |
+
mo.appendChild(self.dom.createTextNode('∇'))
|
| 1639 |
+
mrow.appendChild(mo)
|
| 1640 |
+
mrow.appendChild(self.parenthesize(expr._expr, PRECEDENCE['Mul']))
|
| 1641 |
+
return mrow
|
| 1642 |
+
|
| 1643 |
+
def _print_Laplacian(self, expr):
|
| 1644 |
+
mrow = self.dom.createElement('mrow')
|
| 1645 |
+
mo = self.dom.createElement('mo')
|
| 1646 |
+
mo.appendChild(self.dom.createTextNode('∆'))
|
| 1647 |
+
mrow.appendChild(mo)
|
| 1648 |
+
mrow.appendChild(self.parenthesize(expr._expr, PRECEDENCE['Mul']))
|
| 1649 |
+
return mrow
|
| 1650 |
+
|
| 1651 |
+
def _print_Integers(self, e):
|
| 1652 |
+
x = self.dom.createElement('mi')
|
| 1653 |
+
x.setAttribute('mathvariant', 'normal')
|
| 1654 |
+
x.appendChild(self.dom.createTextNode('ℤ'))
|
| 1655 |
+
return x
|
| 1656 |
+
|
| 1657 |
+
def _print_Complexes(self, e):
|
| 1658 |
+
x = self.dom.createElement('mi')
|
| 1659 |
+
x.setAttribute('mathvariant', 'normal')
|
| 1660 |
+
x.appendChild(self.dom.createTextNode('ℂ'))
|
| 1661 |
+
return x
|
| 1662 |
+
|
| 1663 |
+
def _print_Reals(self, e):
|
| 1664 |
+
x = self.dom.createElement('mi')
|
| 1665 |
+
x.setAttribute('mathvariant', 'normal')
|
| 1666 |
+
x.appendChild(self.dom.createTextNode('ℝ'))
|
| 1667 |
+
return x
|
| 1668 |
+
|
| 1669 |
+
def _print_Naturals(self, e):
|
| 1670 |
+
x = self.dom.createElement('mi')
|
| 1671 |
+
x.setAttribute('mathvariant', 'normal')
|
| 1672 |
+
x.appendChild(self.dom.createTextNode('ℕ'))
|
| 1673 |
+
return x
|
| 1674 |
+
|
| 1675 |
+
def _print_Naturals0(self, e):
|
| 1676 |
+
sub = self.dom.createElement('msub')
|
| 1677 |
+
x = self.dom.createElement('mi')
|
| 1678 |
+
x.setAttribute('mathvariant', 'normal')
|
| 1679 |
+
x.appendChild(self.dom.createTextNode('ℕ'))
|
| 1680 |
+
sub.appendChild(x)
|
| 1681 |
+
sub.appendChild(self._print(S.Zero))
|
| 1682 |
+
return sub
|
| 1683 |
+
|
| 1684 |
+
def _print_SingularityFunction(self, expr):
|
| 1685 |
+
shift = expr.args[0] - expr.args[1]
|
| 1686 |
+
power = expr.args[2]
|
| 1687 |
+
left = self.dom.createElement('mo')
|
| 1688 |
+
left.appendChild(self.dom.createTextNode('\u27e8'))
|
| 1689 |
+
right = self.dom.createElement('mo')
|
| 1690 |
+
right.appendChild(self.dom.createTextNode('\u27e9'))
|
| 1691 |
+
brac = self.dom.createElement('mrow')
|
| 1692 |
+
brac.appendChild(left)
|
| 1693 |
+
brac.appendChild(self._print(shift))
|
| 1694 |
+
brac.appendChild(right)
|
| 1695 |
+
sup = self.dom.createElement('msup')
|
| 1696 |
+
sup.appendChild(brac)
|
| 1697 |
+
sup.appendChild(self._print(power))
|
| 1698 |
+
return sup
|
| 1699 |
+
|
| 1700 |
+
def _print_NaN(self, e):
|
| 1701 |
+
x = self.dom.createElement('mi')
|
| 1702 |
+
x.appendChild(self.dom.createTextNode('NaN'))
|
| 1703 |
+
return x
|
| 1704 |
+
|
| 1705 |
+
def _print_number_function(self, e, name):
|
| 1706 |
+
# Print name_arg[0] for one argument or name_arg[0](arg[1])
|
| 1707 |
+
# for more than one argument
|
| 1708 |
+
sub = self.dom.createElement('msub')
|
| 1709 |
+
mi = self.dom.createElement('mi')
|
| 1710 |
+
mi.appendChild(self.dom.createTextNode(name))
|
| 1711 |
+
sub.appendChild(mi)
|
| 1712 |
+
sub.appendChild(self._print(e.args[0]))
|
| 1713 |
+
if len(e.args) == 1:
|
| 1714 |
+
return sub
|
| 1715 |
+
mrow = self.dom.createElement('mrow')
|
| 1716 |
+
mrow.appendChild(sub)
|
| 1717 |
+
mrow.appendChild(self._paren_comma_separated(*e.args[1:]))
|
| 1718 |
+
return mrow
|
| 1719 |
+
|
| 1720 |
+
def _print_bernoulli(self, e):
|
| 1721 |
+
return self._print_number_function(e, 'B')
|
| 1722 |
+
|
| 1723 |
+
_print_bell = _print_bernoulli
|
| 1724 |
+
|
| 1725 |
+
def _print_catalan(self, e):
|
| 1726 |
+
return self._print_number_function(e, 'C')
|
| 1727 |
+
|
| 1728 |
+
def _print_euler(self, e):
|
| 1729 |
+
return self._print_number_function(e, 'E')
|
| 1730 |
+
|
| 1731 |
+
def _print_fibonacci(self, e):
|
| 1732 |
+
return self._print_number_function(e, 'F')
|
| 1733 |
+
|
| 1734 |
+
def _print_lucas(self, e):
|
| 1735 |
+
return self._print_number_function(e, 'L')
|
| 1736 |
+
|
| 1737 |
+
def _print_stieltjes(self, e):
|
| 1738 |
+
return self._print_number_function(e, 'γ')
|
| 1739 |
+
|
| 1740 |
+
def _print_tribonacci(self, e):
|
| 1741 |
+
return self._print_number_function(e, 'T')
|
| 1742 |
+
|
| 1743 |
+
def _print_ComplexInfinity(self, e):
|
| 1744 |
+
x = self.dom.createElement('mover')
|
| 1745 |
+
mo = self.dom.createElement('mo')
|
| 1746 |
+
mo.appendChild(self.dom.createTextNode('∞'))
|
| 1747 |
+
x.appendChild(mo)
|
| 1748 |
+
mo = self.dom.createElement('mo')
|
| 1749 |
+
mo.appendChild(self.dom.createTextNode('~'))
|
| 1750 |
+
x.appendChild(mo)
|
| 1751 |
+
return x
|
| 1752 |
+
|
| 1753 |
+
def _print_EmptySet(self, e):
|
| 1754 |
+
x = self.dom.createElement('mo')
|
| 1755 |
+
x.appendChild(self.dom.createTextNode('∅'))
|
| 1756 |
+
return x
|
| 1757 |
+
|
| 1758 |
+
def _print_UniversalSet(self, e):
|
| 1759 |
+
x = self.dom.createElement('mo')
|
| 1760 |
+
x.appendChild(self.dom.createTextNode('𝕌'))
|
| 1761 |
+
return x
|
| 1762 |
+
|
| 1763 |
+
def _print_Adjoint(self, expr):
|
| 1764 |
+
from sympy.matrices import MatrixSymbol
|
| 1765 |
+
mat = expr.arg
|
| 1766 |
+
sup = self.dom.createElement('msup')
|
| 1767 |
+
if not isinstance(mat, MatrixSymbol):
|
| 1768 |
+
brac = self.dom.createElement('mrow')
|
| 1769 |
+
brac.appendChild(self._l_paren())
|
| 1770 |
+
brac.appendChild(self._print(mat))
|
| 1771 |
+
brac.appendChild(self._r_paren())
|
| 1772 |
+
sup.appendChild(brac)
|
| 1773 |
+
else:
|
| 1774 |
+
sup.appendChild(self._print(mat))
|
| 1775 |
+
mo = self.dom.createElement('mo')
|
| 1776 |
+
mo.appendChild(self.dom.createTextNode('†'))
|
| 1777 |
+
sup.appendChild(mo)
|
| 1778 |
+
return sup
|
| 1779 |
+
|
| 1780 |
+
def _print_Transpose(self, expr):
|
| 1781 |
+
from sympy.matrices import MatrixSymbol
|
| 1782 |
+
mat = expr.arg
|
| 1783 |
+
sup = self.dom.createElement('msup')
|
| 1784 |
+
if not isinstance(mat, MatrixSymbol):
|
| 1785 |
+
brac = self.dom.createElement('mrow')
|
| 1786 |
+
brac.appendChild(self._l_paren())
|
| 1787 |
+
brac.appendChild(self._print(mat))
|
| 1788 |
+
brac.appendChild(self._r_paren())
|
| 1789 |
+
sup.appendChild(brac)
|
| 1790 |
+
else:
|
| 1791 |
+
sup.appendChild(self._print(mat))
|
| 1792 |
+
mo = self.dom.createElement('mo')
|
| 1793 |
+
mo.appendChild(self.dom.createTextNode('T'))
|
| 1794 |
+
sup.appendChild(mo)
|
| 1795 |
+
return sup
|
| 1796 |
+
|
| 1797 |
+
def _print_Inverse(self, expr):
|
| 1798 |
+
from sympy.matrices import MatrixSymbol
|
| 1799 |
+
mat = expr.arg
|
| 1800 |
+
sup = self.dom.createElement('msup')
|
| 1801 |
+
if not isinstance(mat, MatrixSymbol):
|
| 1802 |
+
brac = self.dom.createElement('mrow')
|
| 1803 |
+
brac.appendChild(self._l_paren())
|
| 1804 |
+
brac.appendChild(self._print(mat))
|
| 1805 |
+
brac.appendChild(self._r_paren())
|
| 1806 |
+
sup.appendChild(brac)
|
| 1807 |
+
else:
|
| 1808 |
+
sup.appendChild(self._print(mat))
|
| 1809 |
+
sup.appendChild(self._print(-1))
|
| 1810 |
+
return sup
|
| 1811 |
+
|
| 1812 |
+
def _print_MatMul(self, expr):
|
| 1813 |
+
from sympy.matrices.expressions.matmul import MatMul
|
| 1814 |
+
|
| 1815 |
+
x = self.dom.createElement('mrow')
|
| 1816 |
+
args = expr.args
|
| 1817 |
+
if isinstance(args[0], Mul):
|
| 1818 |
+
args = args[0].as_ordered_factors() + list(args[1:])
|
| 1819 |
+
else:
|
| 1820 |
+
args = list(args)
|
| 1821 |
+
|
| 1822 |
+
if isinstance(expr, MatMul) and expr.could_extract_minus_sign():
|
| 1823 |
+
if args[0] == -1:
|
| 1824 |
+
args = args[1:]
|
| 1825 |
+
else:
|
| 1826 |
+
args[0] = -args[0]
|
| 1827 |
+
mo = self.dom.createElement('mo')
|
| 1828 |
+
mo.appendChild(self.dom.createTextNode('-'))
|
| 1829 |
+
x.appendChild(mo)
|
| 1830 |
+
|
| 1831 |
+
for arg in args[:-1]:
|
| 1832 |
+
x.appendChild(self.parenthesize(arg, precedence_traditional(expr),
|
| 1833 |
+
False))
|
| 1834 |
+
mo = self.dom.createElement('mo')
|
| 1835 |
+
mo.appendChild(self.dom.createTextNode('⁢'))
|
| 1836 |
+
x.appendChild(mo)
|
| 1837 |
+
x.appendChild(self.parenthesize(args[-1], precedence_traditional(expr),
|
| 1838 |
+
False))
|
| 1839 |
+
return x
|
| 1840 |
+
|
| 1841 |
+
def _print_MatPow(self, expr):
|
| 1842 |
+
from sympy.matrices import MatrixSymbol
|
| 1843 |
+
base, exp = expr.base, expr.exp
|
| 1844 |
+
sup = self.dom.createElement('msup')
|
| 1845 |
+
if not isinstance(base, MatrixSymbol):
|
| 1846 |
+
brac = self.dom.createElement('mrow')
|
| 1847 |
+
brac.appendChild(self._l_paren())
|
| 1848 |
+
brac.appendChild(self._print(base))
|
| 1849 |
+
brac.appendChild(self._r_paren())
|
| 1850 |
+
sup.appendChild(brac)
|
| 1851 |
+
else:
|
| 1852 |
+
sup.appendChild(self._print(base))
|
| 1853 |
+
sup.appendChild(self._print(exp))
|
| 1854 |
+
return sup
|
| 1855 |
+
|
| 1856 |
+
def _print_HadamardProduct(self, expr):
|
| 1857 |
+
x = self.dom.createElement('mrow')
|
| 1858 |
+
args = expr.args
|
| 1859 |
+
for arg in args[:-1]:
|
| 1860 |
+
x.appendChild(
|
| 1861 |
+
self.parenthesize(arg, precedence_traditional(expr), False))
|
| 1862 |
+
mo = self.dom.createElement('mo')
|
| 1863 |
+
mo.appendChild(self.dom.createTextNode('∘'))
|
| 1864 |
+
x.appendChild(mo)
|
| 1865 |
+
x.appendChild(
|
| 1866 |
+
self.parenthesize(args[-1], precedence_traditional(expr), False))
|
| 1867 |
+
return x
|
| 1868 |
+
|
| 1869 |
+
def _print_ZeroMatrix(self, Z):
|
| 1870 |
+
x = self.dom.createElement('mn')
|
| 1871 |
+
x.appendChild(self.dom.createTextNode('𝟘'))
|
| 1872 |
+
return x
|
| 1873 |
+
|
| 1874 |
+
def _print_OneMatrix(self, Z):
|
| 1875 |
+
x = self.dom.createElement('mn')
|
| 1876 |
+
x.appendChild(self.dom.createTextNode('𝟙'))
|
| 1877 |
+
return x
|
| 1878 |
+
|
| 1879 |
+
def _print_Identity(self, I):
|
| 1880 |
+
x = self.dom.createElement('mi')
|
| 1881 |
+
x.appendChild(self.dom.createTextNode('𝕀'))
|
| 1882 |
+
return x
|
| 1883 |
+
|
| 1884 |
+
def _print_floor(self, e):
|
| 1885 |
+
left = self.dom.createElement('mo')
|
| 1886 |
+
left.appendChild(self.dom.createTextNode('\u230A'))
|
| 1887 |
+
right = self.dom.createElement('mo')
|
| 1888 |
+
right.appendChild(self.dom.createTextNode('\u230B'))
|
| 1889 |
+
mrow = self.dom.createElement('mrow')
|
| 1890 |
+
mrow.appendChild(left)
|
| 1891 |
+
mrow.appendChild(self._print(e.args[0]))
|
| 1892 |
+
mrow.appendChild(right)
|
| 1893 |
+
return mrow
|
| 1894 |
+
|
| 1895 |
+
def _print_ceiling(self, e):
|
| 1896 |
+
left = self.dom.createElement('mo')
|
| 1897 |
+
left.appendChild(self.dom.createTextNode('\u2308'))
|
| 1898 |
+
right = self.dom.createElement('mo')
|
| 1899 |
+
right.appendChild(self.dom.createTextNode('\u2309'))
|
| 1900 |
+
mrow = self.dom.createElement('mrow')
|
| 1901 |
+
mrow.appendChild(left)
|
| 1902 |
+
mrow.appendChild(self._print(e.args[0]))
|
| 1903 |
+
mrow.appendChild(right)
|
| 1904 |
+
return mrow
|
| 1905 |
+
|
| 1906 |
+
def _print_Lambda(self, e):
|
| 1907 |
+
mrow = self.dom.createElement('mrow')
|
| 1908 |
+
symbols = e.args[0]
|
| 1909 |
+
if len(symbols) == 1:
|
| 1910 |
+
symbols = self._print(symbols[0])
|
| 1911 |
+
else:
|
| 1912 |
+
symbols = self._print(symbols)
|
| 1913 |
+
mrow.appendChild(self._l_paren())
|
| 1914 |
+
mrow.appendChild(symbols)
|
| 1915 |
+
mo = self.dom.createElement('mo')
|
| 1916 |
+
mo.appendChild(self.dom.createTextNode('↦'))
|
| 1917 |
+
mrow.appendChild(mo)
|
| 1918 |
+
mrow.appendChild(self._print(e.args[1]))
|
| 1919 |
+
mrow.appendChild(self._r_paren())
|
| 1920 |
+
return mrow
|
| 1921 |
+
|
| 1922 |
+
def _print_tuple(self, e):
|
| 1923 |
+
return self._paren_comma_separated(*e)
|
| 1924 |
+
|
| 1925 |
+
def _print_IndexedBase(self, e):
|
| 1926 |
+
return self._print(e.label)
|
| 1927 |
+
|
| 1928 |
+
def _print_Indexed(self, e):
|
| 1929 |
+
x = self.dom.createElement('msub')
|
| 1930 |
+
x.appendChild(self._print(e.base))
|
| 1931 |
+
if len(e.indices) == 1:
|
| 1932 |
+
x.appendChild(self._print(e.indices[0]))
|
| 1933 |
+
return x
|
| 1934 |
+
x.appendChild(self._print(e.indices))
|
| 1935 |
+
return x
|
| 1936 |
+
|
| 1937 |
+
def _print_MatrixElement(self, e):
|
| 1938 |
+
x = self.dom.createElement('msub')
|
| 1939 |
+
x.appendChild(self.parenthesize(e.parent, PRECEDENCE["Atom"], strict = True))
|
| 1940 |
+
brac = self.dom.createElement('mrow')
|
| 1941 |
+
for i, arg in enumerate(e.indices):
|
| 1942 |
+
if i:
|
| 1943 |
+
brac.appendChild(self._comma())
|
| 1944 |
+
brac.appendChild(self._print(arg))
|
| 1945 |
+
x.appendChild(brac)
|
| 1946 |
+
return x
|
| 1947 |
+
|
| 1948 |
+
def _print_elliptic_f(self, e):
|
| 1949 |
+
x = self.dom.createElement('mrow')
|
| 1950 |
+
mi = self.dom.createElement('mi')
|
| 1951 |
+
mi.appendChild(self.dom.createTextNode('𝖥'))
|
| 1952 |
+
x.appendChild(mi)
|
| 1953 |
+
x.appendChild(self._paren_bar_separated(*e.args))
|
| 1954 |
+
return x
|
| 1955 |
+
|
| 1956 |
+
def _print_elliptic_e(self, e):
|
| 1957 |
+
x = self.dom.createElement('mrow')
|
| 1958 |
+
mi = self.dom.createElement('mi')
|
| 1959 |
+
mi.appendChild(self.dom.createTextNode('𝖤'))
|
| 1960 |
+
x.appendChild(mi)
|
| 1961 |
+
x.appendChild(self._paren_bar_separated(*e.args))
|
| 1962 |
+
return x
|
| 1963 |
+
|
| 1964 |
+
def _print_elliptic_pi(self, e):
|
| 1965 |
+
x = self.dom.createElement('mrow')
|
| 1966 |
+
mi = self.dom.createElement('mi')
|
| 1967 |
+
mi.appendChild(self.dom.createTextNode('𝛱'))
|
| 1968 |
+
x.appendChild(mi)
|
| 1969 |
+
y = self.dom.createElement('mrow')
|
| 1970 |
+
y.appendChild(self._l_paren())
|
| 1971 |
+
if len(e.args) == 2:
|
| 1972 |
+
n, m = e.args
|
| 1973 |
+
y.appendChild(self._print(n))
|
| 1974 |
+
y.appendChild(self._bar())
|
| 1975 |
+
y.appendChild(self._print(m))
|
| 1976 |
+
else:
|
| 1977 |
+
n, m, z = e.args
|
| 1978 |
+
y.appendChild(self._print(n))
|
| 1979 |
+
y.appendChild(self._semicolon())
|
| 1980 |
+
y.appendChild(self._print(m))
|
| 1981 |
+
y.appendChild(self._bar())
|
| 1982 |
+
y.appendChild(self._print(z))
|
| 1983 |
+
y.appendChild(self._r_paren())
|
| 1984 |
+
x.appendChild(y)
|
| 1985 |
+
return x
|
| 1986 |
+
|
| 1987 |
+
def _print_Ei(self, e):
|
| 1988 |
+
x = self.dom.createElement('mrow')
|
| 1989 |
+
mi = self.dom.createElement('mi')
|
| 1990 |
+
mi.appendChild(self.dom.createTextNode('Ei'))
|
| 1991 |
+
x.appendChild(mi)
|
| 1992 |
+
x.appendChild(self._print(e.args))
|
| 1993 |
+
return x
|
| 1994 |
+
|
| 1995 |
+
def _print_expint(self, e):
|
| 1996 |
+
x = self.dom.createElement('mrow')
|
| 1997 |
+
y = self.dom.createElement('msub')
|
| 1998 |
+
mo = self.dom.createElement('mo')
|
| 1999 |
+
mo.appendChild(self.dom.createTextNode('E'))
|
| 2000 |
+
y.appendChild(mo)
|
| 2001 |
+
y.appendChild(self._print(e.args[0]))
|
| 2002 |
+
x.appendChild(y)
|
| 2003 |
+
x.appendChild(self._print(e.args[1:]))
|
| 2004 |
+
return x
|
| 2005 |
+
|
| 2006 |
+
def _print_jacobi(self, e):
|
| 2007 |
+
x = self.dom.createElement('mrow')
|
| 2008 |
+
y = self.dom.createElement('msubsup')
|
| 2009 |
+
mo = self.dom.createElement('mo')
|
| 2010 |
+
mo.appendChild(self.dom.createTextNode('P'))
|
| 2011 |
+
y.appendChild(mo)
|
| 2012 |
+
y.appendChild(self._print(e.args[0]))
|
| 2013 |
+
y.appendChild(self._print(e.args[1:3]))
|
| 2014 |
+
x.appendChild(y)
|
| 2015 |
+
x.appendChild(self._print(e.args[3:]))
|
| 2016 |
+
return x
|
| 2017 |
+
|
| 2018 |
+
def _print_gegenbauer(self, e):
|
| 2019 |
+
x = self.dom.createElement('mrow')
|
| 2020 |
+
y = self.dom.createElement('msubsup')
|
| 2021 |
+
mo = self.dom.createElement('mo')
|
| 2022 |
+
mo.appendChild(self.dom.createTextNode('C'))
|
| 2023 |
+
y.appendChild(mo)
|
| 2024 |
+
y.appendChild(self._print(e.args[0]))
|
| 2025 |
+
y.appendChild(self._print(e.args[1:2]))
|
| 2026 |
+
x.appendChild(y)
|
| 2027 |
+
x.appendChild(self._print(e.args[2:]))
|
| 2028 |
+
return x
|
| 2029 |
+
|
| 2030 |
+
def _print_chebyshevt(self, e):
|
| 2031 |
+
x = self.dom.createElement('mrow')
|
| 2032 |
+
y = self.dom.createElement('msub')
|
| 2033 |
+
mo = self.dom.createElement('mo')
|
| 2034 |
+
mo.appendChild(self.dom.createTextNode('T'))
|
| 2035 |
+
y.appendChild(mo)
|
| 2036 |
+
y.appendChild(self._print(e.args[0]))
|
| 2037 |
+
x.appendChild(y)
|
| 2038 |
+
x.appendChild(self._print(e.args[1:]))
|
| 2039 |
+
return x
|
| 2040 |
+
|
| 2041 |
+
def _print_chebyshevu(self, e):
|
| 2042 |
+
x = self.dom.createElement('mrow')
|
| 2043 |
+
y = self.dom.createElement('msub')
|
| 2044 |
+
mo = self.dom.createElement('mo')
|
| 2045 |
+
mo.appendChild(self.dom.createTextNode('U'))
|
| 2046 |
+
y.appendChild(mo)
|
| 2047 |
+
y.appendChild(self._print(e.args[0]))
|
| 2048 |
+
x.appendChild(y)
|
| 2049 |
+
x.appendChild(self._print(e.args[1:]))
|
| 2050 |
+
return x
|
| 2051 |
+
|
| 2052 |
+
def _print_legendre(self, e):
|
| 2053 |
+
x = self.dom.createElement('mrow')
|
| 2054 |
+
y = self.dom.createElement('msub')
|
| 2055 |
+
mo = self.dom.createElement('mo')
|
| 2056 |
+
mo.appendChild(self.dom.createTextNode('P'))
|
| 2057 |
+
y.appendChild(mo)
|
| 2058 |
+
y.appendChild(self._print(e.args[0]))
|
| 2059 |
+
x.appendChild(y)
|
| 2060 |
+
x.appendChild(self._print(e.args[1:]))
|
| 2061 |
+
return x
|
| 2062 |
+
|
| 2063 |
+
def _print_assoc_legendre(self, e):
|
| 2064 |
+
x = self.dom.createElement('mrow')
|
| 2065 |
+
y = self.dom.createElement('msubsup')
|
| 2066 |
+
mo = self.dom.createElement('mo')
|
| 2067 |
+
mo.appendChild(self.dom.createTextNode('P'))
|
| 2068 |
+
y.appendChild(mo)
|
| 2069 |
+
y.appendChild(self._print(e.args[0]))
|
| 2070 |
+
y.appendChild(self._print(e.args[1:2]))
|
| 2071 |
+
x.appendChild(y)
|
| 2072 |
+
x.appendChild(self._print(e.args[2:]))
|
| 2073 |
+
return x
|
| 2074 |
+
|
| 2075 |
+
def _print_laguerre(self, e):
|
| 2076 |
+
x = self.dom.createElement('mrow')
|
| 2077 |
+
y = self.dom.createElement('msub')
|
| 2078 |
+
mo = self.dom.createElement('mo')
|
| 2079 |
+
mo.appendChild(self.dom.createTextNode('L'))
|
| 2080 |
+
y.appendChild(mo)
|
| 2081 |
+
y.appendChild(self._print(e.args[0]))
|
| 2082 |
+
x.appendChild(y)
|
| 2083 |
+
x.appendChild(self._print(e.args[1:]))
|
| 2084 |
+
return x
|
| 2085 |
+
|
| 2086 |
+
def _print_assoc_laguerre(self, e):
|
| 2087 |
+
x = self.dom.createElement('mrow')
|
| 2088 |
+
y = self.dom.createElement('msubsup')
|
| 2089 |
+
mo = self.dom.createElement('mo')
|
| 2090 |
+
mo.appendChild(self.dom.createTextNode('L'))
|
| 2091 |
+
y.appendChild(mo)
|
| 2092 |
+
y.appendChild(self._print(e.args[0]))
|
| 2093 |
+
y.appendChild(self._print(e.args[1:2]))
|
| 2094 |
+
x.appendChild(y)
|
| 2095 |
+
x.appendChild(self._print(e.args[2:]))
|
| 2096 |
+
return x
|
| 2097 |
+
|
| 2098 |
+
def _print_hermite(self, e):
|
| 2099 |
+
x = self.dom.createElement('mrow')
|
| 2100 |
+
y = self.dom.createElement('msub')
|
| 2101 |
+
mo = self.dom.createElement('mo')
|
| 2102 |
+
mo.appendChild(self.dom.createTextNode('H'))
|
| 2103 |
+
y.appendChild(mo)
|
| 2104 |
+
y.appendChild(self._print(e.args[0]))
|
| 2105 |
+
x.appendChild(y)
|
| 2106 |
+
x.appendChild(self._print(e.args[1:]))
|
| 2107 |
+
return x
|
| 2108 |
+
|
| 2109 |
+
|
| 2110 |
+
@print_function(MathMLPrinterBase)
|
| 2111 |
+
def mathml(expr, printer='content', **settings):
|
| 2112 |
+
"""Returns the MathML representation of expr. If printer is presentation
|
| 2113 |
+
then prints Presentation MathML else prints content MathML.
|
| 2114 |
+
"""
|
| 2115 |
+
if printer == 'presentation':
|
| 2116 |
+
return MathMLPresentationPrinter(settings).doprint(expr)
|
| 2117 |
+
else:
|
| 2118 |
+
return MathMLContentPrinter(settings).doprint(expr)
|
| 2119 |
+
|
| 2120 |
+
|
| 2121 |
+
def print_mathml(expr, printer='content', **settings):
|
| 2122 |
+
"""
|
| 2123 |
+
Prints a pretty representation of the MathML code for expr. If printer is
|
| 2124 |
+
presentation then prints Presentation MathML else prints content MathML.
|
| 2125 |
+
|
| 2126 |
+
Examples
|
| 2127 |
+
========
|
| 2128 |
+
|
| 2129 |
+
>>> ##
|
| 2130 |
+
>>> from sympy import print_mathml
|
| 2131 |
+
>>> from sympy.abc import x
|
| 2132 |
+
>>> print_mathml(x+1) #doctest: +NORMALIZE_WHITESPACE
|
| 2133 |
+
<apply>
|
| 2134 |
+
<plus/>
|
| 2135 |
+
<ci>x</ci>
|
| 2136 |
+
<cn>1</cn>
|
| 2137 |
+
</apply>
|
| 2138 |
+
>>> print_mathml(x+1, printer='presentation')
|
| 2139 |
+
<mrow>
|
| 2140 |
+
<mi>x</mi>
|
| 2141 |
+
<mo>+</mo>
|
| 2142 |
+
<mn>1</mn>
|
| 2143 |
+
</mrow>
|
| 2144 |
+
|
| 2145 |
+
"""
|
| 2146 |
+
if printer == 'presentation':
|
| 2147 |
+
s = MathMLPresentationPrinter(settings)
|
| 2148 |
+
else:
|
| 2149 |
+
s = MathMLContentPrinter(settings)
|
| 2150 |
+
xml = s._print(sympify(expr))
|
| 2151 |
+
pretty_xml = xml.toprettyxml()
|
| 2152 |
+
|
| 2153 |
+
print(pretty_xml)
|
| 2154 |
+
|
| 2155 |
+
|
| 2156 |
+
# For backward compatibility
|
| 2157 |
+
MathMLPrinter = MathMLContentPrinter
|
.venv/lib/python3.13/site-packages/sympy/printing/rcode.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
R code printer
|
| 3 |
+
|
| 4 |
+
The RCodePrinter converts single SymPy expressions into single R expressions,
|
| 5 |
+
using the functions defined in math.h where possible.
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
from typing import Any
|
| 13 |
+
|
| 14 |
+
from sympy.core.numbers import equal_valued
|
| 15 |
+
from sympy.printing.codeprinter import CodePrinter
|
| 16 |
+
from sympy.printing.precedence import precedence, PRECEDENCE
|
| 17 |
+
from sympy.sets.fancysets import Range
|
| 18 |
+
|
| 19 |
+
# dictionary mapping SymPy function to (argument_conditions, C_function).
|
| 20 |
+
# Used in RCodePrinter._print_Function(self)
|
| 21 |
+
known_functions = {
|
| 22 |
+
#"Abs": [(lambda x: not x.is_integer, "fabs")],
|
| 23 |
+
"Abs": "abs",
|
| 24 |
+
"sin": "sin",
|
| 25 |
+
"cos": "cos",
|
| 26 |
+
"tan": "tan",
|
| 27 |
+
"asin": "asin",
|
| 28 |
+
"acos": "acos",
|
| 29 |
+
"atan": "atan",
|
| 30 |
+
"atan2": "atan2",
|
| 31 |
+
"exp": "exp",
|
| 32 |
+
"log": "log",
|
| 33 |
+
"erf": "erf",
|
| 34 |
+
"sinh": "sinh",
|
| 35 |
+
"cosh": "cosh",
|
| 36 |
+
"tanh": "tanh",
|
| 37 |
+
"asinh": "asinh",
|
| 38 |
+
"acosh": "acosh",
|
| 39 |
+
"atanh": "atanh",
|
| 40 |
+
"floor": "floor",
|
| 41 |
+
"ceiling": "ceiling",
|
| 42 |
+
"sign": "sign",
|
| 43 |
+
"Max": "max",
|
| 44 |
+
"Min": "min",
|
| 45 |
+
"factorial": "factorial",
|
| 46 |
+
"gamma": "gamma",
|
| 47 |
+
"digamma": "digamma",
|
| 48 |
+
"trigamma": "trigamma",
|
| 49 |
+
"beta": "beta",
|
| 50 |
+
"sqrt": "sqrt", # To enable automatic rewrite
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
# These are the core reserved words in the R language. Taken from:
|
| 54 |
+
# https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Reserved-words
|
| 55 |
+
|
| 56 |
+
reserved_words = ['if',
|
| 57 |
+
'else',
|
| 58 |
+
'repeat',
|
| 59 |
+
'while',
|
| 60 |
+
'function',
|
| 61 |
+
'for',
|
| 62 |
+
'in',
|
| 63 |
+
'next',
|
| 64 |
+
'break',
|
| 65 |
+
'TRUE',
|
| 66 |
+
'FALSE',
|
| 67 |
+
'NULL',
|
| 68 |
+
'Inf',
|
| 69 |
+
'NaN',
|
| 70 |
+
'NA',
|
| 71 |
+
'NA_integer_',
|
| 72 |
+
'NA_real_',
|
| 73 |
+
'NA_complex_',
|
| 74 |
+
'NA_character_',
|
| 75 |
+
'volatile']
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class RCodePrinter(CodePrinter):
|
| 79 |
+
"""A printer to convert SymPy expressions to strings of R code"""
|
| 80 |
+
printmethod = "_rcode"
|
| 81 |
+
language = "R"
|
| 82 |
+
|
| 83 |
+
_default_settings: dict[str, Any] = dict(CodePrinter._default_settings, **{
|
| 84 |
+
'precision': 15,
|
| 85 |
+
'user_functions': {},
|
| 86 |
+
'contract': True,
|
| 87 |
+
'dereference': set(),
|
| 88 |
+
})
|
| 89 |
+
_operators = {
|
| 90 |
+
'and': '&',
|
| 91 |
+
'or': '|',
|
| 92 |
+
'not': '!',
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
_relationals: dict[str, str] = {}
|
| 96 |
+
|
| 97 |
+
def __init__(self, settings={}):
|
| 98 |
+
CodePrinter.__init__(self, settings)
|
| 99 |
+
self.known_functions = dict(known_functions)
|
| 100 |
+
userfuncs = settings.get('user_functions', {})
|
| 101 |
+
self.known_functions.update(userfuncs)
|
| 102 |
+
self._dereference = set(settings.get('dereference', []))
|
| 103 |
+
self.reserved_words = set(reserved_words)
|
| 104 |
+
|
| 105 |
+
def _rate_index_position(self, p):
|
| 106 |
+
return p*5
|
| 107 |
+
|
| 108 |
+
def _get_statement(self, codestring):
|
| 109 |
+
return "%s;" % codestring
|
| 110 |
+
|
| 111 |
+
def _get_comment(self, text):
|
| 112 |
+
return "// {}".format(text)
|
| 113 |
+
|
| 114 |
+
def _declare_number_const(self, name, value):
|
| 115 |
+
return "{} = {};".format(name, value)
|
| 116 |
+
|
| 117 |
+
def _format_code(self, lines):
|
| 118 |
+
return self.indent_code(lines)
|
| 119 |
+
|
| 120 |
+
def _traverse_matrix_indices(self, mat):
|
| 121 |
+
rows, cols = mat.shape
|
| 122 |
+
return ((i, j) for i in range(rows) for j in range(cols))
|
| 123 |
+
|
| 124 |
+
def _get_loop_opening_ending(self, indices):
|
| 125 |
+
"""Returns a tuple (open_lines, close_lines) containing lists of codelines
|
| 126 |
+
"""
|
| 127 |
+
open_lines = []
|
| 128 |
+
close_lines = []
|
| 129 |
+
loopstart = "for (%(var)s in %(start)s:%(end)s){"
|
| 130 |
+
for i in indices:
|
| 131 |
+
# R arrays start at 1 and end at dimension
|
| 132 |
+
open_lines.append(loopstart % {
|
| 133 |
+
'var': self._print(i.label),
|
| 134 |
+
'start': self._print(i.lower+1),
|
| 135 |
+
'end': self._print(i.upper + 1)})
|
| 136 |
+
close_lines.append("}")
|
| 137 |
+
return open_lines, close_lines
|
| 138 |
+
|
| 139 |
+
def _print_Pow(self, expr):
|
| 140 |
+
if "Pow" in self.known_functions:
|
| 141 |
+
return self._print_Function(expr)
|
| 142 |
+
PREC = precedence(expr)
|
| 143 |
+
if equal_valued(expr.exp, -1):
|
| 144 |
+
return '1.0/%s' % (self.parenthesize(expr.base, PREC))
|
| 145 |
+
elif equal_valued(expr.exp, 0.5):
|
| 146 |
+
return 'sqrt(%s)' % self._print(expr.base)
|
| 147 |
+
else:
|
| 148 |
+
return '%s^%s' % (self.parenthesize(expr.base, PREC),
|
| 149 |
+
self.parenthesize(expr.exp, PREC))
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def _print_Rational(self, expr):
|
| 153 |
+
p, q = int(expr.p), int(expr.q)
|
| 154 |
+
return '%d.0/%d.0' % (p, q)
|
| 155 |
+
|
| 156 |
+
def _print_Indexed(self, expr):
|
| 157 |
+
inds = [ self._print(i) for i in expr.indices ]
|
| 158 |
+
return "%s[%s]" % (self._print(expr.base.label), ", ".join(inds))
|
| 159 |
+
|
| 160 |
+
def _print_Exp1(self, expr):
|
| 161 |
+
return "exp(1)"
|
| 162 |
+
|
| 163 |
+
def _print_Pi(self, expr):
|
| 164 |
+
return 'pi'
|
| 165 |
+
|
| 166 |
+
def _print_Infinity(self, expr):
|
| 167 |
+
return 'Inf'
|
| 168 |
+
|
| 169 |
+
def _print_NegativeInfinity(self, expr):
|
| 170 |
+
return '-Inf'
|
| 171 |
+
|
| 172 |
+
def _print_Assignment(self, expr):
|
| 173 |
+
from sympy.codegen.ast import Assignment
|
| 174 |
+
|
| 175 |
+
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
| 176 |
+
from sympy.tensor.indexed import IndexedBase
|
| 177 |
+
lhs = expr.lhs
|
| 178 |
+
rhs = expr.rhs
|
| 179 |
+
# We special case assignments that take multiple lines
|
| 180 |
+
#if isinstance(expr.rhs, Piecewise):
|
| 181 |
+
# from sympy.functions.elementary.piecewise import Piecewise
|
| 182 |
+
# # Here we modify Piecewise so each expression is now
|
| 183 |
+
# # an Assignment, and then continue on the print.
|
| 184 |
+
# expressions = []
|
| 185 |
+
# conditions = []
|
| 186 |
+
# for (e, c) in rhs.args:
|
| 187 |
+
# expressions.append(Assignment(lhs, e))
|
| 188 |
+
# conditions.append(c)
|
| 189 |
+
# temp = Piecewise(*zip(expressions, conditions))
|
| 190 |
+
# return self._print(temp)
|
| 191 |
+
#elif isinstance(lhs, MatrixSymbol):
|
| 192 |
+
if isinstance(lhs, MatrixSymbol):
|
| 193 |
+
# Here we form an Assignment for each element in the array,
|
| 194 |
+
# printing each one.
|
| 195 |
+
lines = []
|
| 196 |
+
for (i, j) in self._traverse_matrix_indices(lhs):
|
| 197 |
+
temp = Assignment(lhs[i, j], rhs[i, j])
|
| 198 |
+
code0 = self._print(temp)
|
| 199 |
+
lines.append(code0)
|
| 200 |
+
return "\n".join(lines)
|
| 201 |
+
elif self._settings["contract"] and (lhs.has(IndexedBase) or
|
| 202 |
+
rhs.has(IndexedBase)):
|
| 203 |
+
# Here we check if there is looping to be done, and if so
|
| 204 |
+
# print the required loops.
|
| 205 |
+
return self._doprint_loops(rhs, lhs)
|
| 206 |
+
else:
|
| 207 |
+
lhs_code = self._print(lhs)
|
| 208 |
+
rhs_code = self._print(rhs)
|
| 209 |
+
return self._get_statement("%s = %s" % (lhs_code, rhs_code))
|
| 210 |
+
|
| 211 |
+
def _print_Piecewise(self, expr):
|
| 212 |
+
# This method is called only for inline if constructs
|
| 213 |
+
# Top level piecewise is handled in doprint()
|
| 214 |
+
if expr.args[-1].cond == True:
|
| 215 |
+
last_line = "%s" % self._print(expr.args[-1].expr)
|
| 216 |
+
else:
|
| 217 |
+
last_line = "ifelse(%s,%s,NA)" % (self._print(expr.args[-1].cond), self._print(expr.args[-1].expr))
|
| 218 |
+
code=last_line
|
| 219 |
+
for e, c in reversed(expr.args[:-1]):
|
| 220 |
+
code= "ifelse(%s,%s," % (self._print(c), self._print(e))+code+")"
|
| 221 |
+
return(code)
|
| 222 |
+
|
| 223 |
+
def _print_ITE(self, expr):
|
| 224 |
+
from sympy.functions import Piecewise
|
| 225 |
+
return self._print(expr.rewrite(Piecewise))
|
| 226 |
+
|
| 227 |
+
def _print_MatrixElement(self, expr):
|
| 228 |
+
return "{}[{}]".format(self.parenthesize(expr.parent, PRECEDENCE["Atom"],
|
| 229 |
+
strict=True), expr.j + expr.i*expr.parent.shape[1])
|
| 230 |
+
|
| 231 |
+
def _print_Symbol(self, expr):
|
| 232 |
+
name = super()._print_Symbol(expr)
|
| 233 |
+
if expr in self._dereference:
|
| 234 |
+
return '(*{})'.format(name)
|
| 235 |
+
else:
|
| 236 |
+
return name
|
| 237 |
+
|
| 238 |
+
def _print_Relational(self, expr):
|
| 239 |
+
lhs_code = self._print(expr.lhs)
|
| 240 |
+
rhs_code = self._print(expr.rhs)
|
| 241 |
+
op = expr.rel_op
|
| 242 |
+
return "{} {} {}".format(lhs_code, op, rhs_code)
|
| 243 |
+
|
| 244 |
+
def _print_AugmentedAssignment(self, expr):
|
| 245 |
+
lhs_code = self._print(expr.lhs)
|
| 246 |
+
op = expr.op
|
| 247 |
+
rhs_code = self._print(expr.rhs)
|
| 248 |
+
return "{} {} {};".format(lhs_code, op, rhs_code)
|
| 249 |
+
|
| 250 |
+
def _print_For(self, expr):
|
| 251 |
+
target = self._print(expr.target)
|
| 252 |
+
if isinstance(expr.iterable, Range):
|
| 253 |
+
start, stop, step = expr.iterable.args
|
| 254 |
+
else:
|
| 255 |
+
raise NotImplementedError("Only iterable currently supported is Range")
|
| 256 |
+
body = self._print(expr.body)
|
| 257 |
+
return 'for({target} in seq(from={start}, to={stop}, by={step}){{\n{body}\n}}'.format(target=target, start=start,
|
| 258 |
+
stop=stop-1, step=step, body=body)
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def indent_code(self, code):
|
| 262 |
+
"""Accepts a string of code or a list of code lines"""
|
| 263 |
+
|
| 264 |
+
if isinstance(code, str):
|
| 265 |
+
code_lines = self.indent_code(code.splitlines(True))
|
| 266 |
+
return ''.join(code_lines)
|
| 267 |
+
|
| 268 |
+
tab = " "
|
| 269 |
+
inc_token = ('{', '(', '{\n', '(\n')
|
| 270 |
+
dec_token = ('}', ')')
|
| 271 |
+
|
| 272 |
+
code = [ line.lstrip(' \t') for line in code ]
|
| 273 |
+
|
| 274 |
+
increase = [ int(any(map(line.endswith, inc_token))) for line in code ]
|
| 275 |
+
decrease = [ int(any(map(line.startswith, dec_token)))
|
| 276 |
+
for line in code ]
|
| 277 |
+
|
| 278 |
+
pretty = []
|
| 279 |
+
level = 0
|
| 280 |
+
for n, line in enumerate(code):
|
| 281 |
+
if line in ('', '\n'):
|
| 282 |
+
pretty.append(line)
|
| 283 |
+
continue
|
| 284 |
+
level -= decrease[n]
|
| 285 |
+
pretty.append("%s%s" % (tab*level, line))
|
| 286 |
+
level += increase[n]
|
| 287 |
+
return pretty
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
def rcode(expr, assign_to=None, **settings):
|
| 291 |
+
"""Converts an expr to a string of r code
|
| 292 |
+
|
| 293 |
+
Parameters
|
| 294 |
+
==========
|
| 295 |
+
|
| 296 |
+
expr : Expr
|
| 297 |
+
A SymPy expression to be converted.
|
| 298 |
+
assign_to : optional
|
| 299 |
+
When given, the argument is used as the name of the variable to which
|
| 300 |
+
the expression is assigned. Can be a string, ``Symbol``,
|
| 301 |
+
``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of
|
| 302 |
+
line-wrapping, or for expressions that generate multi-line statements.
|
| 303 |
+
precision : integer, optional
|
| 304 |
+
The precision for numbers such as pi [default=15].
|
| 305 |
+
user_functions : dict, optional
|
| 306 |
+
A dictionary where the keys are string representations of either
|
| 307 |
+
``FunctionClass`` or ``UndefinedFunction`` instances and the values
|
| 308 |
+
are their desired R string representations. Alternatively, the
|
| 309 |
+
dictionary value can be a list of tuples i.e. [(argument_test,
|
| 310 |
+
rfunction_string)] or [(argument_test, rfunction_formater)]. See below
|
| 311 |
+
for examples.
|
| 312 |
+
human : bool, optional
|
| 313 |
+
If True, the result is a single string that may contain some constant
|
| 314 |
+
declarations for the number symbols. If False, the same information is
|
| 315 |
+
returned in a tuple of (symbols_to_declare, not_supported_functions,
|
| 316 |
+
code_text). [default=True].
|
| 317 |
+
contract: bool, optional
|
| 318 |
+
If True, ``Indexed`` instances are assumed to obey tensor contraction
|
| 319 |
+
rules and the corresponding nested loops over indices are generated.
|
| 320 |
+
Setting contract=False will not generate loops, instead the user is
|
| 321 |
+
responsible to provide values for the indices in the code.
|
| 322 |
+
[default=True].
|
| 323 |
+
|
| 324 |
+
Examples
|
| 325 |
+
========
|
| 326 |
+
|
| 327 |
+
>>> from sympy import rcode, symbols, Rational, sin, ceiling, Abs, Function
|
| 328 |
+
>>> x, tau = symbols("x, tau")
|
| 329 |
+
>>> rcode((2*tau)**Rational(7, 2))
|
| 330 |
+
'8*sqrt(2)*tau^(7.0/2.0)'
|
| 331 |
+
>>> rcode(sin(x), assign_to="s")
|
| 332 |
+
's = sin(x);'
|
| 333 |
+
|
| 334 |
+
Simple custom printing can be defined for certain types by passing a
|
| 335 |
+
dictionary of {"type" : "function"} to the ``user_functions`` kwarg.
|
| 336 |
+
Alternatively, the dictionary value can be a list of tuples i.e.
|
| 337 |
+
[(argument_test, cfunction_string)].
|
| 338 |
+
|
| 339 |
+
>>> custom_functions = {
|
| 340 |
+
... "ceiling": "CEIL",
|
| 341 |
+
... "Abs": [(lambda x: not x.is_integer, "fabs"),
|
| 342 |
+
... (lambda x: x.is_integer, "ABS")],
|
| 343 |
+
... "func": "f"
|
| 344 |
+
... }
|
| 345 |
+
>>> func = Function('func')
|
| 346 |
+
>>> rcode(func(Abs(x) + ceiling(x)), user_functions=custom_functions)
|
| 347 |
+
'f(fabs(x) + CEIL(x))'
|
| 348 |
+
|
| 349 |
+
or if the R-function takes a subset of the original arguments:
|
| 350 |
+
|
| 351 |
+
>>> rcode(2**x + 3**x, user_functions={'Pow': [
|
| 352 |
+
... (lambda b, e: b == 2, lambda b, e: 'exp2(%s)' % e),
|
| 353 |
+
... (lambda b, e: b != 2, 'pow')]})
|
| 354 |
+
'exp2(x) + pow(3, x)'
|
| 355 |
+
|
| 356 |
+
``Piecewise`` expressions are converted into conditionals. If an
|
| 357 |
+
``assign_to`` variable is provided an if statement is created, otherwise
|
| 358 |
+
the ternary operator is used. Note that if the ``Piecewise`` lacks a
|
| 359 |
+
default term, represented by ``(expr, True)`` then an error will be thrown.
|
| 360 |
+
This is to prevent generating an expression that may not evaluate to
|
| 361 |
+
anything.
|
| 362 |
+
|
| 363 |
+
>>> from sympy import Piecewise
|
| 364 |
+
>>> expr = Piecewise((x + 1, x > 0), (x, True))
|
| 365 |
+
>>> print(rcode(expr, assign_to=tau))
|
| 366 |
+
tau = ifelse(x > 0,x + 1,x);
|
| 367 |
+
|
| 368 |
+
Support for loops is provided through ``Indexed`` types. With
|
| 369 |
+
``contract=True`` these expressions will be turned into loops, whereas
|
| 370 |
+
``contract=False`` will just print the assignment expression that should be
|
| 371 |
+
looped over:
|
| 372 |
+
|
| 373 |
+
>>> from sympy import Eq, IndexedBase, Idx
|
| 374 |
+
>>> len_y = 5
|
| 375 |
+
>>> y = IndexedBase('y', shape=(len_y,))
|
| 376 |
+
>>> t = IndexedBase('t', shape=(len_y,))
|
| 377 |
+
>>> Dy = IndexedBase('Dy', shape=(len_y-1,))
|
| 378 |
+
>>> i = Idx('i', len_y-1)
|
| 379 |
+
>>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i]))
|
| 380 |
+
>>> rcode(e.rhs, assign_to=e.lhs, contract=False)
|
| 381 |
+
'Dy[i] = (y[i + 1] - y[i])/(t[i + 1] - t[i]);'
|
| 382 |
+
|
| 383 |
+
Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions
|
| 384 |
+
must be provided to ``assign_to``. Note that any expression that can be
|
| 385 |
+
generated normally can also exist inside a Matrix:
|
| 386 |
+
|
| 387 |
+
>>> from sympy import Matrix, MatrixSymbol
|
| 388 |
+
>>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)])
|
| 389 |
+
>>> A = MatrixSymbol('A', 3, 1)
|
| 390 |
+
>>> print(rcode(mat, A))
|
| 391 |
+
A[0] = x^2;
|
| 392 |
+
A[1] = ifelse(x > 0,x + 1,x);
|
| 393 |
+
A[2] = sin(x);
|
| 394 |
+
|
| 395 |
+
"""
|
| 396 |
+
|
| 397 |
+
return RCodePrinter(settings).doprint(expr, assign_to)
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
def print_rcode(expr, **settings):
|
| 401 |
+
"""Prints R representation of the given expression."""
|
| 402 |
+
print(rcode(expr, **settings))
|
.venv/lib/python3.13/site-packages/sympy/printing/tableform.py
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.containers import Tuple
|
| 2 |
+
from sympy.core.singleton import S
|
| 3 |
+
from sympy.core.symbol import Symbol
|
| 4 |
+
from sympy.core.sympify import SympifyError
|
| 5 |
+
|
| 6 |
+
from types import FunctionType
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class TableForm:
|
| 10 |
+
r"""
|
| 11 |
+
Create a nice table representation of data.
|
| 12 |
+
|
| 13 |
+
Examples
|
| 14 |
+
========
|
| 15 |
+
|
| 16 |
+
>>> from sympy import TableForm
|
| 17 |
+
>>> t = TableForm([[5, 7], [4, 2], [10, 3]])
|
| 18 |
+
>>> print(t)
|
| 19 |
+
5 7
|
| 20 |
+
4 2
|
| 21 |
+
10 3
|
| 22 |
+
|
| 23 |
+
You can use the SymPy's printing system to produce tables in any
|
| 24 |
+
format (ascii, latex, html, ...).
|
| 25 |
+
|
| 26 |
+
>>> print(t.as_latex())
|
| 27 |
+
\begin{tabular}{l l}
|
| 28 |
+
$5$ & $7$ \\
|
| 29 |
+
$4$ & $2$ \\
|
| 30 |
+
$10$ & $3$ \\
|
| 31 |
+
\end{tabular}
|
| 32 |
+
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
def __init__(self, data, **kwarg):
|
| 36 |
+
"""
|
| 37 |
+
Creates a TableForm.
|
| 38 |
+
|
| 39 |
+
Parameters:
|
| 40 |
+
|
| 41 |
+
data ...
|
| 42 |
+
2D data to be put into the table; data can be
|
| 43 |
+
given as a Matrix
|
| 44 |
+
|
| 45 |
+
headings ...
|
| 46 |
+
gives the labels for rows and columns:
|
| 47 |
+
|
| 48 |
+
Can be a single argument that applies to both
|
| 49 |
+
dimensions:
|
| 50 |
+
|
| 51 |
+
- None ... no labels
|
| 52 |
+
- "automatic" ... labels are 1, 2, 3, ...
|
| 53 |
+
|
| 54 |
+
Can be a list of labels for rows and columns:
|
| 55 |
+
The labels for each dimension can be given
|
| 56 |
+
as None, "automatic", or [l1, l2, ...] e.g.
|
| 57 |
+
["automatic", None] will number the rows
|
| 58 |
+
|
| 59 |
+
[default: None]
|
| 60 |
+
|
| 61 |
+
alignments ...
|
| 62 |
+
alignment of the columns with:
|
| 63 |
+
|
| 64 |
+
- "left" or "<"
|
| 65 |
+
- "center" or "^"
|
| 66 |
+
- "right" or ">"
|
| 67 |
+
|
| 68 |
+
When given as a single value, the value is used for
|
| 69 |
+
all columns. The row headings (if given) will be
|
| 70 |
+
right justified unless an explicit alignment is
|
| 71 |
+
given for it and all other columns.
|
| 72 |
+
|
| 73 |
+
[default: "left"]
|
| 74 |
+
|
| 75 |
+
formats ...
|
| 76 |
+
a list of format strings or functions that accept
|
| 77 |
+
3 arguments (entry, row number, col number) and
|
| 78 |
+
return a string for the table entry. (If a function
|
| 79 |
+
returns None then the _print method will be used.)
|
| 80 |
+
|
| 81 |
+
wipe_zeros ...
|
| 82 |
+
Do not show zeros in the table.
|
| 83 |
+
|
| 84 |
+
[default: True]
|
| 85 |
+
|
| 86 |
+
pad ...
|
| 87 |
+
the string to use to indicate a missing value (e.g.
|
| 88 |
+
elements that are None or those that are missing
|
| 89 |
+
from the end of a row (i.e. any row that is shorter
|
| 90 |
+
than the rest is assumed to have missing values).
|
| 91 |
+
When None, nothing will be shown for values that
|
| 92 |
+
are missing from the end of a row; values that are
|
| 93 |
+
None, however, will be shown.
|
| 94 |
+
|
| 95 |
+
[default: None]
|
| 96 |
+
|
| 97 |
+
Examples
|
| 98 |
+
========
|
| 99 |
+
|
| 100 |
+
>>> from sympy import TableForm, Symbol
|
| 101 |
+
>>> TableForm([[5, 7], [4, 2], [10, 3]])
|
| 102 |
+
5 7
|
| 103 |
+
4 2
|
| 104 |
+
10 3
|
| 105 |
+
>>> TableForm([list('.'*i) for i in range(1, 4)], headings='automatic')
|
| 106 |
+
| 1 2 3
|
| 107 |
+
---------
|
| 108 |
+
1 | .
|
| 109 |
+
2 | . .
|
| 110 |
+
3 | . . .
|
| 111 |
+
>>> TableForm([[Symbol('.'*(j if not i%2 else 1)) for i in range(3)]
|
| 112 |
+
... for j in range(4)], alignments='rcl')
|
| 113 |
+
.
|
| 114 |
+
. . .
|
| 115 |
+
.. . ..
|
| 116 |
+
... . ...
|
| 117 |
+
"""
|
| 118 |
+
from sympy.matrices.dense import Matrix
|
| 119 |
+
|
| 120 |
+
# We only support 2D data. Check the consistency:
|
| 121 |
+
if isinstance(data, Matrix):
|
| 122 |
+
data = data.tolist()
|
| 123 |
+
_h = len(data)
|
| 124 |
+
|
| 125 |
+
# fill out any short lines
|
| 126 |
+
pad = kwarg.get('pad', None)
|
| 127 |
+
ok_None = False
|
| 128 |
+
if pad is None:
|
| 129 |
+
pad = " "
|
| 130 |
+
ok_None = True
|
| 131 |
+
pad = Symbol(pad)
|
| 132 |
+
_w = max(len(line) for line in data)
|
| 133 |
+
for i, line in enumerate(data):
|
| 134 |
+
if len(line) != _w:
|
| 135 |
+
line.extend([pad]*(_w - len(line)))
|
| 136 |
+
for j, lj in enumerate(line):
|
| 137 |
+
if lj is None:
|
| 138 |
+
if not ok_None:
|
| 139 |
+
lj = pad
|
| 140 |
+
else:
|
| 141 |
+
try:
|
| 142 |
+
lj = S(lj)
|
| 143 |
+
except SympifyError:
|
| 144 |
+
lj = Symbol(str(lj))
|
| 145 |
+
line[j] = lj
|
| 146 |
+
data[i] = line
|
| 147 |
+
_lines = Tuple(*[Tuple(*d) for d in data])
|
| 148 |
+
|
| 149 |
+
headings = kwarg.get("headings", [None, None])
|
| 150 |
+
if headings == "automatic":
|
| 151 |
+
_headings = [range(1, _h + 1), range(1, _w + 1)]
|
| 152 |
+
else:
|
| 153 |
+
h1, h2 = headings
|
| 154 |
+
if h1 == "automatic":
|
| 155 |
+
h1 = range(1, _h + 1)
|
| 156 |
+
if h2 == "automatic":
|
| 157 |
+
h2 = range(1, _w + 1)
|
| 158 |
+
_headings = [h1, h2]
|
| 159 |
+
|
| 160 |
+
allow = ('l', 'r', 'c')
|
| 161 |
+
alignments = kwarg.get("alignments", "l")
|
| 162 |
+
|
| 163 |
+
def _std_align(a):
|
| 164 |
+
a = a.strip().lower()
|
| 165 |
+
if len(a) > 1:
|
| 166 |
+
return {'left': 'l', 'right': 'r', 'center': 'c'}.get(a, a)
|
| 167 |
+
else:
|
| 168 |
+
return {'<': 'l', '>': 'r', '^': 'c'}.get(a, a)
|
| 169 |
+
std_align = _std_align(alignments)
|
| 170 |
+
if std_align in allow:
|
| 171 |
+
_alignments = [std_align]*_w
|
| 172 |
+
else:
|
| 173 |
+
_alignments = []
|
| 174 |
+
for a in alignments:
|
| 175 |
+
std_align = _std_align(a)
|
| 176 |
+
_alignments.append(std_align)
|
| 177 |
+
if std_align not in ('l', 'r', 'c'):
|
| 178 |
+
raise ValueError('alignment "%s" unrecognized' %
|
| 179 |
+
alignments)
|
| 180 |
+
if _headings[0] and len(_alignments) == _w + 1:
|
| 181 |
+
_head_align = _alignments[0]
|
| 182 |
+
_alignments = _alignments[1:]
|
| 183 |
+
else:
|
| 184 |
+
_head_align = 'r'
|
| 185 |
+
if len(_alignments) != _w:
|
| 186 |
+
raise ValueError(
|
| 187 |
+
'wrong number of alignments: expected %s but got %s' %
|
| 188 |
+
(_w, len(_alignments)))
|
| 189 |
+
|
| 190 |
+
_column_formats = kwarg.get("formats", [None]*_w)
|
| 191 |
+
|
| 192 |
+
_wipe_zeros = kwarg.get("wipe_zeros", True)
|
| 193 |
+
|
| 194 |
+
self._w = _w
|
| 195 |
+
self._h = _h
|
| 196 |
+
self._lines = _lines
|
| 197 |
+
self._headings = _headings
|
| 198 |
+
self._head_align = _head_align
|
| 199 |
+
self._alignments = _alignments
|
| 200 |
+
self._column_formats = _column_formats
|
| 201 |
+
self._wipe_zeros = _wipe_zeros
|
| 202 |
+
|
| 203 |
+
def __repr__(self):
|
| 204 |
+
from .str import sstr
|
| 205 |
+
return sstr(self, order=None)
|
| 206 |
+
|
| 207 |
+
def __str__(self):
|
| 208 |
+
from .str import sstr
|
| 209 |
+
return sstr(self, order=None)
|
| 210 |
+
|
| 211 |
+
def as_matrix(self):
|
| 212 |
+
"""Returns the data of the table in Matrix form.
|
| 213 |
+
|
| 214 |
+
Examples
|
| 215 |
+
========
|
| 216 |
+
|
| 217 |
+
>>> from sympy import TableForm
|
| 218 |
+
>>> t = TableForm([[5, 7], [4, 2], [10, 3]], headings='automatic')
|
| 219 |
+
>>> t
|
| 220 |
+
| 1 2
|
| 221 |
+
--------
|
| 222 |
+
1 | 5 7
|
| 223 |
+
2 | 4 2
|
| 224 |
+
3 | 10 3
|
| 225 |
+
>>> t.as_matrix()
|
| 226 |
+
Matrix([
|
| 227 |
+
[ 5, 7],
|
| 228 |
+
[ 4, 2],
|
| 229 |
+
[10, 3]])
|
| 230 |
+
"""
|
| 231 |
+
from sympy.matrices.dense import Matrix
|
| 232 |
+
return Matrix(self._lines)
|
| 233 |
+
|
| 234 |
+
def as_str(self):
|
| 235 |
+
# XXX obsolete ?
|
| 236 |
+
return str(self)
|
| 237 |
+
|
| 238 |
+
def as_latex(self):
|
| 239 |
+
from .latex import latex
|
| 240 |
+
return latex(self)
|
| 241 |
+
|
| 242 |
+
def _sympystr(self, p):
|
| 243 |
+
"""
|
| 244 |
+
Returns the string representation of 'self'.
|
| 245 |
+
|
| 246 |
+
Examples
|
| 247 |
+
========
|
| 248 |
+
|
| 249 |
+
>>> from sympy import TableForm
|
| 250 |
+
>>> t = TableForm([[5, 7], [4, 2], [10, 3]])
|
| 251 |
+
>>> s = t.as_str()
|
| 252 |
+
|
| 253 |
+
"""
|
| 254 |
+
column_widths = [0] * self._w
|
| 255 |
+
lines = []
|
| 256 |
+
for line in self._lines:
|
| 257 |
+
new_line = []
|
| 258 |
+
for i in range(self._w):
|
| 259 |
+
# Format the item somehow if needed:
|
| 260 |
+
s = str(line[i])
|
| 261 |
+
if self._wipe_zeros and (s == "0"):
|
| 262 |
+
s = " "
|
| 263 |
+
w = len(s)
|
| 264 |
+
if w > column_widths[i]:
|
| 265 |
+
column_widths[i] = w
|
| 266 |
+
new_line.append(s)
|
| 267 |
+
lines.append(new_line)
|
| 268 |
+
|
| 269 |
+
# Check heading:
|
| 270 |
+
if self._headings[0]:
|
| 271 |
+
self._headings[0] = [str(x) for x in self._headings[0]]
|
| 272 |
+
_head_width = max(len(x) for x in self._headings[0])
|
| 273 |
+
|
| 274 |
+
if self._headings[1]:
|
| 275 |
+
new_line = []
|
| 276 |
+
for i in range(self._w):
|
| 277 |
+
# Format the item somehow if needed:
|
| 278 |
+
s = str(self._headings[1][i])
|
| 279 |
+
w = len(s)
|
| 280 |
+
if w > column_widths[i]:
|
| 281 |
+
column_widths[i] = w
|
| 282 |
+
new_line.append(s)
|
| 283 |
+
self._headings[1] = new_line
|
| 284 |
+
|
| 285 |
+
format_str = []
|
| 286 |
+
|
| 287 |
+
def _align(align, w):
|
| 288 |
+
return '%%%s%ss' % (
|
| 289 |
+
("-" if align == "l" else ""),
|
| 290 |
+
str(w))
|
| 291 |
+
format_str = [_align(align, w) for align, w in
|
| 292 |
+
zip(self._alignments, column_widths)]
|
| 293 |
+
if self._headings[0]:
|
| 294 |
+
format_str.insert(0, _align(self._head_align, _head_width))
|
| 295 |
+
format_str.insert(1, '|')
|
| 296 |
+
format_str = ' '.join(format_str) + '\n'
|
| 297 |
+
|
| 298 |
+
s = []
|
| 299 |
+
if self._headings[1]:
|
| 300 |
+
d = self._headings[1]
|
| 301 |
+
if self._headings[0]:
|
| 302 |
+
d = [""] + d
|
| 303 |
+
first_line = format_str % tuple(d)
|
| 304 |
+
s.append(first_line)
|
| 305 |
+
s.append("-" * (len(first_line) - 1) + "\n")
|
| 306 |
+
for i, line in enumerate(lines):
|
| 307 |
+
d = [l if self._alignments[j] != 'c' else
|
| 308 |
+
l.center(column_widths[j]) for j, l in enumerate(line)]
|
| 309 |
+
if self._headings[0]:
|
| 310 |
+
l = self._headings[0][i]
|
| 311 |
+
l = (l if self._head_align != 'c' else
|
| 312 |
+
l.center(_head_width))
|
| 313 |
+
d = [l] + d
|
| 314 |
+
s.append(format_str % tuple(d))
|
| 315 |
+
return ''.join(s)[:-1] # don't include trailing newline
|
| 316 |
+
|
| 317 |
+
def _latex(self, printer):
|
| 318 |
+
"""
|
| 319 |
+
Returns the string representation of 'self'.
|
| 320 |
+
"""
|
| 321 |
+
# Check heading:
|
| 322 |
+
if self._headings[1]:
|
| 323 |
+
new_line = []
|
| 324 |
+
for i in range(self._w):
|
| 325 |
+
# Format the item somehow if needed:
|
| 326 |
+
new_line.append(str(self._headings[1][i]))
|
| 327 |
+
self._headings[1] = new_line
|
| 328 |
+
|
| 329 |
+
alignments = []
|
| 330 |
+
if self._headings[0]:
|
| 331 |
+
self._headings[0] = [str(x) for x in self._headings[0]]
|
| 332 |
+
alignments = [self._head_align]
|
| 333 |
+
alignments.extend(self._alignments)
|
| 334 |
+
|
| 335 |
+
s = r"\begin{tabular}{" + " ".join(alignments) + "}\n"
|
| 336 |
+
|
| 337 |
+
if self._headings[1]:
|
| 338 |
+
d = self._headings[1]
|
| 339 |
+
if self._headings[0]:
|
| 340 |
+
d = [""] + d
|
| 341 |
+
first_line = " & ".join(d) + r" \\" + "\n"
|
| 342 |
+
s += first_line
|
| 343 |
+
s += r"\hline" + "\n"
|
| 344 |
+
for i, line in enumerate(self._lines):
|
| 345 |
+
d = []
|
| 346 |
+
for j, x in enumerate(line):
|
| 347 |
+
if self._wipe_zeros and (x in (0, "0")):
|
| 348 |
+
d.append(" ")
|
| 349 |
+
continue
|
| 350 |
+
f = self._column_formats[j]
|
| 351 |
+
if f:
|
| 352 |
+
if isinstance(f, FunctionType):
|
| 353 |
+
v = f(x, i, j)
|
| 354 |
+
if v is None:
|
| 355 |
+
v = printer._print(x)
|
| 356 |
+
else:
|
| 357 |
+
v = f % x
|
| 358 |
+
d.append(v)
|
| 359 |
+
else:
|
| 360 |
+
v = printer._print(x)
|
| 361 |
+
d.append("$%s$" % v)
|
| 362 |
+
if self._headings[0]:
|
| 363 |
+
d = [self._headings[0][i]] + d
|
| 364 |
+
s += " & ".join(d) + r" \\" + "\n"
|
| 365 |
+
s += r"\end{tabular}"
|
| 366 |
+
return s
|
.venv/pyvenv.cfg
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
home = /Library/Frameworks/Python.framework/Versions/3.13/bin
|
| 2 |
+
implementation = CPython
|
| 3 |
+
uv = 0.8.13
|
| 4 |
+
version_info = 3.13.5
|
| 5 |
+
include-system-site-packages = false
|
| 6 |
+
prompt = onnx-runner-detection
|
.venv/share/man/man1/isympy.1
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'\" -*- coding: us-ascii -*-
|
| 2 |
+
.if \n(.g .ds T< \\FC
|
| 3 |
+
.if \n(.g .ds T> \\F[\n[.fam]]
|
| 4 |
+
.de URL
|
| 5 |
+
\\$2 \(la\\$1\(ra\\$3
|
| 6 |
+
..
|
| 7 |
+
.if \n(.g .mso www.tmac
|
| 8 |
+
.TH isympy 1 2007-10-8 "" ""
|
| 9 |
+
.SH NAME
|
| 10 |
+
isympy \- interactive shell for SymPy
|
| 11 |
+
.SH SYNOPSIS
|
| 12 |
+
'nh
|
| 13 |
+
.fi
|
| 14 |
+
.ad l
|
| 15 |
+
\fBisympy\fR \kx
|
| 16 |
+
.if (\nx>(\n(.l/2)) .nr x (\n(.l/5)
|
| 17 |
+
'in \n(.iu+\nxu
|
| 18 |
+
[\fB-c\fR | \fB--console\fR] [\fB-p\fR ENCODING | \fB--pretty\fR ENCODING] [\fB-t\fR TYPE | \fB--types\fR TYPE] [\fB-o\fR ORDER | \fB--order\fR ORDER] [\fB-q\fR | \fB--quiet\fR] [\fB-d\fR | \fB--doctest\fR] [\fB-C\fR | \fB--no-cache\fR] [\fB-a\fR | \fB--auto\fR] [\fB-D\fR | \fB--debug\fR] [
|
| 19 |
+
-- | PYTHONOPTIONS]
|
| 20 |
+
'in \n(.iu-\nxu
|
| 21 |
+
.ad b
|
| 22 |
+
'hy
|
| 23 |
+
'nh
|
| 24 |
+
.fi
|
| 25 |
+
.ad l
|
| 26 |
+
\fBisympy\fR \kx
|
| 27 |
+
.if (\nx>(\n(.l/2)) .nr x (\n(.l/5)
|
| 28 |
+
'in \n(.iu+\nxu
|
| 29 |
+
[
|
| 30 |
+
{\fB-h\fR | \fB--help\fR}
|
| 31 |
+
|
|
| 32 |
+
{\fB-v\fR | \fB--version\fR}
|
| 33 |
+
]
|
| 34 |
+
'in \n(.iu-\nxu
|
| 35 |
+
.ad b
|
| 36 |
+
'hy
|
| 37 |
+
.SH DESCRIPTION
|
| 38 |
+
isympy is a Python shell for SymPy. It is just a normal python shell
|
| 39 |
+
(ipython shell if you have the ipython package installed) that executes
|
| 40 |
+
the following commands so that you don't have to:
|
| 41 |
+
.PP
|
| 42 |
+
.nf
|
| 43 |
+
\*(T<
|
| 44 |
+
>>> from __future__ import division
|
| 45 |
+
>>> from sympy import *
|
| 46 |
+
>>> x, y, z = symbols("x,y,z")
|
| 47 |
+
>>> k, m, n = symbols("k,m,n", integer=True)
|
| 48 |
+
\*(T>
|
| 49 |
+
.fi
|
| 50 |
+
.PP
|
| 51 |
+
So starting isympy is equivalent to starting python (or ipython) and
|
| 52 |
+
executing the above commands by hand. It is intended for easy and quick
|
| 53 |
+
experimentation with SymPy. For more complicated programs, it is recommended
|
| 54 |
+
to write a script and import things explicitly (using the "from sympy
|
| 55 |
+
import sin, log, Symbol, ..." idiom).
|
| 56 |
+
.SH OPTIONS
|
| 57 |
+
.TP
|
| 58 |
+
\*(T<\fB\-c \fR\*(T>\fISHELL\fR, \*(T<\fB\-\-console=\fR\*(T>\fISHELL\fR
|
| 59 |
+
Use the specified shell (python or ipython) as
|
| 60 |
+
console backend instead of the default one (ipython
|
| 61 |
+
if present or python otherwise).
|
| 62 |
+
|
| 63 |
+
Example: isympy -c python
|
| 64 |
+
|
| 65 |
+
\fISHELL\fR could be either
|
| 66 |
+
\&'ipython' or 'python'
|
| 67 |
+
.TP
|
| 68 |
+
\*(T<\fB\-p \fR\*(T>\fIENCODING\fR, \*(T<\fB\-\-pretty=\fR\*(T>\fIENCODING\fR
|
| 69 |
+
Setup pretty printing in SymPy. By default, the most pretty, unicode
|
| 70 |
+
printing is enabled (if the terminal supports it). You can use less
|
| 71 |
+
pretty ASCII printing instead or no pretty printing at all.
|
| 72 |
+
|
| 73 |
+
Example: isympy -p no
|
| 74 |
+
|
| 75 |
+
\fIENCODING\fR must be one of 'unicode',
|
| 76 |
+
\&'ascii' or 'no'.
|
| 77 |
+
.TP
|
| 78 |
+
\*(T<\fB\-t \fR\*(T>\fITYPE\fR, \*(T<\fB\-\-types=\fR\*(T>\fITYPE\fR
|
| 79 |
+
Setup the ground types for the polys. By default, gmpy ground types
|
| 80 |
+
are used if gmpy2 or gmpy is installed, otherwise it falls back to python
|
| 81 |
+
ground types, which are a little bit slower. You can manually
|
| 82 |
+
choose python ground types even if gmpy is installed (e.g., for testing purposes).
|
| 83 |
+
|
| 84 |
+
Note that sympy ground types are not supported, and should be used
|
| 85 |
+
only for experimental purposes.
|
| 86 |
+
|
| 87 |
+
Note that the gmpy1 ground type is primarily intended for testing; it the
|
| 88 |
+
use of gmpy even if gmpy2 is available.
|
| 89 |
+
|
| 90 |
+
This is the same as setting the environment variable
|
| 91 |
+
SYMPY_GROUND_TYPES to the given ground type (e.g.,
|
| 92 |
+
SYMPY_GROUND_TYPES='gmpy')
|
| 93 |
+
|
| 94 |
+
The ground types can be determined interactively from the variable
|
| 95 |
+
sympy.polys.domains.GROUND_TYPES inside the isympy shell itself.
|
| 96 |
+
|
| 97 |
+
Example: isympy -t python
|
| 98 |
+
|
| 99 |
+
\fITYPE\fR must be one of 'gmpy',
|
| 100 |
+
\&'gmpy1' or 'python'.
|
| 101 |
+
.TP
|
| 102 |
+
\*(T<\fB\-o \fR\*(T>\fIORDER\fR, \*(T<\fB\-\-order=\fR\*(T>\fIORDER\fR
|
| 103 |
+
Setup the ordering of terms for printing. The default is lex, which
|
| 104 |
+
orders terms lexicographically (e.g., x**2 + x + 1). You can choose
|
| 105 |
+
other orderings, such as rev-lex, which will use reverse
|
| 106 |
+
lexicographic ordering (e.g., 1 + x + x**2).
|
| 107 |
+
|
| 108 |
+
Note that for very large expressions, ORDER='none' may speed up
|
| 109 |
+
printing considerably, with the tradeoff that the order of the terms
|
| 110 |
+
in the printed expression will have no canonical order
|
| 111 |
+
|
| 112 |
+
Example: isympy -o rev-lax
|
| 113 |
+
|
| 114 |
+
\fIORDER\fR must be one of 'lex', 'rev-lex', 'grlex',
|
| 115 |
+
\&'rev-grlex', 'grevlex', 'rev-grevlex', 'old', or 'none'.
|
| 116 |
+
.TP
|
| 117 |
+
\*(T<\fB\-q\fR\*(T>, \*(T<\fB\-\-quiet\fR\*(T>
|
| 118 |
+
Print only Python's and SymPy's versions to stdout at startup, and nothing else.
|
| 119 |
+
.TP
|
| 120 |
+
\*(T<\fB\-d\fR\*(T>, \*(T<\fB\-\-doctest\fR\*(T>
|
| 121 |
+
Use the same format that should be used for doctests. This is
|
| 122 |
+
equivalent to '\fIisympy -c python -p no\fR'.
|
| 123 |
+
.TP
|
| 124 |
+
\*(T<\fB\-C\fR\*(T>, \*(T<\fB\-\-no\-cache\fR\*(T>
|
| 125 |
+
Disable the caching mechanism. Disabling the cache may slow certain
|
| 126 |
+
operations down considerably. This is useful for testing the cache,
|
| 127 |
+
or for benchmarking, as the cache can result in deceptive benchmark timings.
|
| 128 |
+
|
| 129 |
+
This is the same as setting the environment variable SYMPY_USE_CACHE
|
| 130 |
+
to 'no'.
|
| 131 |
+
.TP
|
| 132 |
+
\*(T<\fB\-a\fR\*(T>, \*(T<\fB\-\-auto\fR\*(T>
|
| 133 |
+
Automatically create missing symbols. Normally, typing a name of a
|
| 134 |
+
Symbol that has not been instantiated first would raise NameError,
|
| 135 |
+
but with this option enabled, any undefined name will be
|
| 136 |
+
automatically created as a Symbol. This only works in IPython 0.11.
|
| 137 |
+
|
| 138 |
+
Note that this is intended only for interactive, calculator style
|
| 139 |
+
usage. In a script that uses SymPy, Symbols should be instantiated
|
| 140 |
+
at the top, so that it's clear what they are.
|
| 141 |
+
|
| 142 |
+
This will not override any names that are already defined, which
|
| 143 |
+
includes the single character letters represented by the mnemonic
|
| 144 |
+
QCOSINE (see the "Gotchas and Pitfalls" document in the
|
| 145 |
+
documentation). You can delete existing names by executing "del
|
| 146 |
+
name" in the shell itself. You can see if a name is defined by typing
|
| 147 |
+
"'name' in globals()".
|
| 148 |
+
|
| 149 |
+
The Symbols that are created using this have default assumptions.
|
| 150 |
+
If you want to place assumptions on symbols, you should create them
|
| 151 |
+
using symbols() or var().
|
| 152 |
+
|
| 153 |
+
Finally, this only works in the top level namespace. So, for
|
| 154 |
+
example, if you define a function in isympy with an undefined
|
| 155 |
+
Symbol, it will not work.
|
| 156 |
+
.TP
|
| 157 |
+
\*(T<\fB\-D\fR\*(T>, \*(T<\fB\-\-debug\fR\*(T>
|
| 158 |
+
Enable debugging output. This is the same as setting the
|
| 159 |
+
environment variable SYMPY_DEBUG to 'True'. The debug status is set
|
| 160 |
+
in the variable SYMPY_DEBUG within isympy.
|
| 161 |
+
.TP
|
| 162 |
+
-- \fIPYTHONOPTIONS\fR
|
| 163 |
+
These options will be passed on to \fIipython (1)\fR shell.
|
| 164 |
+
Only supported when ipython is being used (standard python shell not supported).
|
| 165 |
+
|
| 166 |
+
Two dashes (--) are required to separate \fIPYTHONOPTIONS\fR
|
| 167 |
+
from the other isympy options.
|
| 168 |
+
|
| 169 |
+
For example, to run iSymPy without startup banner and colors:
|
| 170 |
+
|
| 171 |
+
isympy -q -c ipython -- --colors=NoColor
|
| 172 |
+
.TP
|
| 173 |
+
\*(T<\fB\-h\fR\*(T>, \*(T<\fB\-\-help\fR\*(T>
|
| 174 |
+
Print help output and exit.
|
| 175 |
+
.TP
|
| 176 |
+
\*(T<\fB\-v\fR\*(T>, \*(T<\fB\-\-version\fR\*(T>
|
| 177 |
+
Print isympy version information and exit.
|
| 178 |
+
.SH FILES
|
| 179 |
+
.TP
|
| 180 |
+
\*(T<\fI${HOME}/.sympy\-history\fR\*(T>
|
| 181 |
+
Saves the history of commands when using the python
|
| 182 |
+
shell as backend.
|
| 183 |
+
.SH BUGS
|
| 184 |
+
The upstreams BTS can be found at \(lahttps://github.com/sympy/sympy/issues\(ra
|
| 185 |
+
Please report all bugs that you find in there, this will help improve
|
| 186 |
+
the overall quality of SymPy.
|
| 187 |
+
.SH "SEE ALSO"
|
| 188 |
+
\fBipython\fR(1), \fBpython\fR(1)
|
README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
tags:
|
| 3 |
+
- element_type:detect
|
| 4 |
+
- model:yolov11-nano
|
| 5 |
+
- object:box
|
| 6 |
+
- object:qr code
|
| 7 |
+
|
| 8 |
+
manako:
|
| 9 |
+
description: Roboflow - generated by element_trainer service to detect box, qr code
|
| 10 |
+
source: element_trainer/box-qr-code-packhouse1-inline3
|
| 11 |
+
prompt_hints:
|
| 12 |
+
input_payload:
|
| 13 |
+
- name: frame
|
| 14 |
+
type: image
|
| 15 |
+
description: RGB frame
|
| 16 |
+
output_payload:
|
| 17 |
+
- name: detections
|
| 18 |
+
type: detections
|
| 19 |
+
description: List of detections
|
| 20 |
+
evaluation_score:
|
| 21 |
+
---
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
video/video_2026-02-18T12-43-09Z/packhouse1_inline3/predictions/004345.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"frame_idx": 4345,
|
| 3 |
+
"image_key": "video/video_2026-02-18T12-43-09Z/packhouse1_inline3/annotated.mp4",
|
| 4 |
+
"predictions": []
|
| 5 |
+
}
|
video/video_2026-02-18T12-43-09Z/packhouse1_inline3/predictions/009824.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"frame_idx": 9824,
|
| 3 |
+
"image_key": "video/video_2026-02-18T12-43-09Z/packhouse1_inline3/annotated.mp4",
|
| 4 |
+
"predictions": []
|
| 5 |
+
}
|
video/video_2026-02-18T12-43-09Z/packhouse1_inline3/predictions/010756.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"frame_idx": 10756,
|
| 3 |
+
"image_key": "video/video_2026-02-18T12-43-09Z/packhouse1_inline3/annotated.mp4",
|
| 4 |
+
"predictions": []
|
| 5 |
+
}
|