MTerryJack commited on
Commit
324ac82
·
verified ·
1 Parent(s): 45a9216

Add files using upload-large-folder tool

Browse files
Files changed (46) hide show
  1. .venv/.gitignore +1 -0
  2. .venv/.lock +0 -0
  3. .venv/CACHEDIR.TAG +1 -0
  4. .venv/bin/activate +130 -0
  5. .venv/bin/activate.bat +71 -0
  6. .venv/bin/activate.csh +76 -0
  7. .venv/bin/activate.fish +124 -0
  8. .venv/bin/activate.nu +117 -0
  9. .venv/bin/activate.ps1 +82 -0
  10. .venv/bin/activate_this.py +59 -0
  11. .venv/bin/deactivate.bat +39 -0
  12. .venv/bin/f2py +10 -0
  13. .venv/bin/isympy +10 -0
  14. .venv/bin/numpy-config +10 -0
  15. .venv/bin/onnxruntime_test +10 -0
  16. .venv/bin/pydoc.bat +22 -0
  17. .venv/lib/python3.13/site-packages/_virtualenv.py +101 -0
  18. .venv/lib/python3.13/site-packages/isympy.py +342 -0
  19. .venv/lib/python3.13/site-packages/packaging/__init__.py +15 -0
  20. .venv/lib/python3.13/site-packages/packaging/_elffile.py +108 -0
  21. .venv/lib/python3.13/site-packages/packaging/_manylinux.py +262 -0
  22. .venv/lib/python3.13/site-packages/packaging/_musllinux.py +85 -0
  23. .venv/lib/python3.13/site-packages/packaging/_parser.py +365 -0
  24. .venv/lib/python3.13/site-packages/packaging/_structures.py +69 -0
  25. .venv/lib/python3.13/site-packages/packaging/_tokenizer.py +193 -0
  26. .venv/lib/python3.13/site-packages/packaging/markers.py +388 -0
  27. .venv/lib/python3.13/site-packages/packaging/metadata.py +978 -0
  28. .venv/lib/python3.13/site-packages/packaging/py.typed +0 -0
  29. .venv/lib/python3.13/site-packages/packaging/pylock.py +635 -0
  30. .venv/lib/python3.13/site-packages/packaging/requirements.py +86 -0
  31. .venv/lib/python3.13/site-packages/packaging/tags.py +651 -0
  32. .venv/lib/python3.13/site-packages/packaging/utils.py +158 -0
  33. .venv/lib/python3.13/site-packages/packaging/version.py +792 -0
  34. .venv/lib/python3.13/site-packages/sympy/printing/__init__.py +111 -0
  35. .venv/lib/python3.13/site-packages/sympy/printing/codeprinter.py +1039 -0
  36. .venv/lib/python3.13/site-packages/sympy/printing/mathematica.py +353 -0
  37. .venv/lib/python3.13/site-packages/sympy/printing/mathml.py +2157 -0
  38. .venv/lib/python3.13/site-packages/sympy/printing/rcode.py +402 -0
  39. .venv/lib/python3.13/site-packages/sympy/printing/tableform.py +366 -0
  40. .venv/pyvenv.cfg +6 -0
  41. .venv/share/man/man1/isympy.1 +188 -0
  42. README.md +21 -0
  43. uv.lock +0 -0
  44. video/video_2026-02-18T12-43-09Z/packhouse1_inline3/predictions/004345.json +5 -0
  45. video/video_2026-02-18T12-43-09Z/packhouse1_inline3/predictions/009824.json +5 -0
  46. 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": '&#xB7;',
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': '&#x2192;',
559
+ 'Derivative': '&dd;',
560
+ 'int': 'mn',
561
+ 'Symbol': 'mi',
562
+ 'Integral': '&int;',
563
+ 'Sum': '&#x2211;',
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': '&#x2260;',
578
+ 'GreaterThan': '&#x2265;',
579
+ 'LessThan': '&#x2264;',
580
+ 'StrictGreaterThan': '>',
581
+ 'StrictLessThan': '<',
582
+ 'lerchphi': '&#x3A6;',
583
+ 'zeta': '&#x3B6;',
584
+ 'dirichlet_eta': '&#x3B7;',
585
+ 'elliptic_k': '&#x39A;',
586
+ 'lowergamma': '&#x3B3;',
587
+ 'uppergamma': '&#x393;',
588
+ 'gamma': '&#x393;',
589
+ 'totient': '&#x3D5;',
590
+ 'reduced_totient': '&#x3BB;',
591
+ 'primenu': '&#x3BD;',
592
+ 'primeomega': '&#x3A9;',
593
+ 'fresnels': 'S',
594
+ 'fresnelc': 'C',
595
+ 'LambertW': 'W',
596
+ 'Heaviside': '&#x398;',
597
+ 'BooleanTrue': 'True',
598
+ 'BooleanFalse': 'False',
599
+ 'NoneType': 'None',
600
+ 'mathieus': 'S',
601
+ 'mathieuc': 'C',
602
+ 'mathieusprime': 'S&#x2032;',
603
+ 'mathieucprime': 'C&#x2032;',
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 '&InvisibleTimes;'
611
+ elif self._settings["mul_symbol"] == 'times':
612
+ return '&#xD7;'
613
+ elif self._settings["mul_symbol"] == 'dot':
614
+ return '&#xB7;'
615
+ elif self._settings["mul_symbol"] == 'ldot':
616
+ return '&#x2024;'
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('&ImaginaryI;'))
841
+ return x
842
+
843
+ def _print_GoldenRatio(self, e):
844
+ x = self.dom.createElement('mi')
845
+ x.appendChild(self.dom.createTextNode('&#x3A6;'))
846
+ return x
847
+
848
+ def _print_Exp1(self, e):
849
+ x = self.dom.createElement('mi')
850
+ x.appendChild(self.dom.createTextNode('&ExponentialE;'))
851
+ return x
852
+
853
+ def _print_Pi(self, e):
854
+ x = self.dom.createElement('mi')
855
+ x.appendChild(self.dom.createTextNode('&pi;'))
856
+ return x
857
+
858
+ def _print_Infinity(self, e):
859
+ x = self.dom.createElement('mi')
860
+ x.appendChild(self.dom.createTextNode('&#x221E;'))
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('&#x210F;'))
875
+ return x
876
+
877
+ def _print_EulerGamma(self, e):
878
+ x = self.dom.createElement('mi')
879
+ x.appendChild(self.dom.createTextNode('&#x3B3;'))
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('&#x2020;'))
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('&#x2208;'))
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('&#x210B;'))
905
+ return x
906
+
907
+ def _print_ComplexSpace(self, e):
908
+ msup = self.dom.createElement('msup')
909
+ msup.appendChild(self.dom.createTextNode('&#x1D49E;'))
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('&#x2131;'))
916
+ return x
917
+
918
+
919
+ def _print_Integral(self, expr):
920
+ intsymbols = {1: "&#x222B;", 2: "&#x222C;", 3: "&#x222D;"}
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('&dd;'))
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 = '&#x2202;'
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, '&#x222A;', prec)
1339
+
1340
+ def _print_Intersection(self, expr):
1341
+ prec = PRECEDENCE_TRADITIONAL['Intersection']
1342
+ return self._print_SetOp(expr, '&#x2229;', prec)
1343
+
1344
+ def _print_Complement(self, expr):
1345
+ prec = PRECEDENCE_TRADITIONAL['Complement']
1346
+ return self._print_SetOp(expr, '&#x2216;', prec)
1347
+
1348
+ def _print_SymmetricDifference(self, expr):
1349
+ prec = PRECEDENCE_TRADITIONAL['SymmetricDifference']
1350
+ return self._print_SetOp(expr, '&#x2206;', prec)
1351
+
1352
+ def _print_ProductSet(self, expr):
1353
+ prec = PRECEDENCE_TRADITIONAL['ProductSet']
1354
+ return self._print_SetOp(expr, '&#x00d7;', 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('&InvisibleTimes;'))
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, '&#x2227;')
1443
+
1444
+ def _print_Or(self, expr):
1445
+ args = sorted(expr.args, key=default_sort_key)
1446
+ return self._print_LogOp(args, '&#x2228;')
1447
+
1448
+ def _print_Xor(self, expr):
1449
+ args = sorted(expr.args, key=default_sort_key)
1450
+ return self._print_LogOp(args, '&#x22BB;')
1451
+
1452
+ def _print_Implies(self, expr):
1453
+ return self._print_LogOp(expr.args, '&#x21D2;')
1454
+
1455
+ def _print_Equivalent(self, expr):
1456
+ args = sorted(expr.args, key=default_sort_key)
1457
+ return self._print_LogOp(args, '&#x21D4;')
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('&#xAC;'))
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('&#xD7;'))
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('&#x2207;'))
1606
+ mrow.appendChild(mo)
1607
+ mo = self.dom.createElement('mo')
1608
+ mo.appendChild(self.dom.createTextNode('&#xD7;'))
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('&#x2207;'))
1617
+ mrow.appendChild(mo)
1618
+ mo = self.dom.createElement('mo')
1619
+ mo.appendChild(self.dom.createTextNode('&#xB7;'))
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('&#xB7;'))
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('&#x2207;'))
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('&#x2206;'))
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('&#x2124;'))
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('&#x2102;'))
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('&#x211D;'))
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('&#x2115;'))
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('&#x2115;'))
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, '&#x03B3;')
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('&#x221E;'))
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('&#x2205;'))
1756
+ return x
1757
+
1758
+ def _print_UniversalSet(self, e):
1759
+ x = self.dom.createElement('mo')
1760
+ x.appendChild(self.dom.createTextNode('&#x1D54C;'))
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('&#x2020;'))
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('&InvisibleTimes;'))
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('&#x2218;'))
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('&#x1D7D8'))
1872
+ return x
1873
+
1874
+ def _print_OneMatrix(self, Z):
1875
+ x = self.dom.createElement('mn')
1876
+ x.appendChild(self.dom.createTextNode('&#x1D7D9'))
1877
+ return x
1878
+
1879
+ def _print_Identity(self, I):
1880
+ x = self.dom.createElement('mi')
1881
+ x.appendChild(self.dom.createTextNode('&#x1D540;'))
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('&#x21A6;'))
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('&#x1d5a5;'))
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('&#x1d5a4;'))
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('&#x1d6f1;'))
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
+ }