- 07 Apr, 2021 2 commits
-
-
Kirill Smelkov authored
Not through interactive console, because it was printing prompts, while `python <prog.py` does not emit anything: $ cat prog.py print 'Hello World' $ python <prog.py Hello World $ gpython <prog.py Python 2.7.18 (default, Apr 20 2020, 20:30:41) [GCC 9.3.0] [GPython 0.0.8] [gevent 20.9.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) Hello World After the patch gpython output is the same as of python: $ gpython <prog.py Hello World Test coverage for interactive mode is pending. We'll add them soon in a follow-up patch after implementing -i. For _interact - by this patch logic - we should be dropping custom raw_input, since now _interact is called only when sys.stdin is tty. But we'll soon be invoking _interact when stdin is not a tty (for `gpython -i <prog.py`), so leave that logic in place as is. /reviewed-by @jerome /reviewed-on nexedi/pygolang!15
-
Kirill Smelkov authored
We will soon need this to be run from several places when implementing support for -i. /reviewed-by @jerome /reviewed-on nexedi/pygolang!15
-
- 26 Mar, 2021 1 commit
-
-
Kirill Smelkov authored
setuptools_dso 2 started to emit those autogenerated files. See https://github.com/mdavidsaver/setuptools_dso/pull/15 for details.
-
- 16 Mar, 2021 1 commit
-
-
Kirill Smelkov authored
Those are quote codes that Go strconv.Quote might produce. And even though Python does not use them when quoting, it too handles those quote codes when decoding: In [1]: '\r' Out[1]: '\r' In [2]: '\a\b\v\f' Out[2]: '\x07\x08\x0b\x0c' https://github.com/python/cpython/blob/2.7.18-0-g8d21aa21f2c/Objects/stringobject.c#L677-L688 -> Teach strconv.unquote + friends to handle them as well. /reviewed-by @jerome /reviewed-on nexedi/pygolang!14
-
- 14 Dec, 2020 2 commits
-
-
Kirill Smelkov authored
https://bugs.python.org/issue20443 This broke test_defer_excchain_dump because testprog/golang_test_defer_excchain.txt is prepared with output where `python file` shows that file name in traceback as it was specified on the command line, e.g. .../pygolang/golang/testprog$ python golang_test_defer_excchain.py Traceback (most recent call last): File ".../pygolang/golang/__init__.py", line 103, in _ return f(*argv, **kw) File "golang_test_defer_excchain.py", line 42, in main raise RuntimeError("err") RuntimeError: err while with py39 it became .../pygolang/golang/testprog$ python golang_test_defer_excchain.py Traceback (most recent call last): File ".../pygolang/golang/__init__.py", line 103, in _ return f(*argv, **kw) File ".../pygolang/golang/testprog/golang_test_defer_excchain.py", line 42, in main raise RuntimeError("err") RuntimeError: err (notice the difference related to "line 42") -> Fix it: - amend the test to conditionally prefix golang_test_defer_excchain.py in expected output with PYGOLANG/golang/testprog/ if it is Python >= 3.9. - amend `gpython file` to match behaviour of underlying `python file`, so that the test passes unconditionally whether it is run by python or gpython. -------- @jerome also says (nexedi/pygolang!13 (comment 122826)): FYI, buildout (and many zope packages) essentially use doctests for testing and they had to deal with similar differences in output. `zope.testing` comes with a doctest checker named "renormalizing" which normalize output with regular expressions, see for example setup of some buildout test [here](https://github.com/buildout/buildout/blob/db3d6e2fbf5d7ff2cc4b2507253c7a221cfc3e32/src/zc/buildout/tests.py#L3615-L3651). We definitely don't need this here for the moment, but maybe one day it can be useful. /reviewed-on nexedi/pygolang!13
-
Kirill Smelkov authored
Debian testing recently switched default python3 to be python3.9. Let's make sure pygolang works with that python version. Currently some tests fail - this will be addressed in the next patch. /reviewed-on !13
-
- 11 Dec, 2020 2 commits
-
-
Kirill Smelkov authored
So that it becomes possible to write with WorkGroup(ctx) as wg: wg.go(f1) wg.go(f2) instead of wg = WorkGroup(ctx) defer(wg.wait) wg.go(f1) wg.go(f2) or wg = WorkGroup(ctx) wg.go(f1) wg.go(f2) wg.wait() This is sometimes handy and is referred to as "structured concurrency" in Python world. sync.Sema, sync.Mutex, sync.RWMutex already support "with". sync.WaitGroup is imho too low-level, but we might consider adding "with" support for it in the future as well. In general pygolang way is to use defer instead of plugging all classes with __enter__/__exit__ "with" support, but for small well-known class of concurrency-related things its seems "with" support is worth it: - having "with" for sync.Mutex+co allows it to be used as a drop-in replacement instead of threading.Lock+co, and - having "with" for sync.WorkGroup - the most commonly-used tool to spawn jobs and wait for their completion - makes it on-par with "structured concurrency". /reviewed-on !12
-
Kirill Smelkov authored
WorkGroup methods work on PyWorkGroup, not on PyWaitGroup. This probably used to work because Cython ignores (?) provided type of self? /reviewed-on !12
-
- 10 Dec, 2020 1 commit
-
-
Kirill Smelkov authored
Similarly to test_pymain_syspath we cannot run this test on SlapOS because with buildout raw underlying python interpreter does not have access to eggs with which gpython script was generated. See 0fa9d6e7 and 92bb5bcc for details. Without this patch running pygolang tests fails on SlapOS as shown below: (pygolang-env) slapuser34@vifibcloud-rapidspace-hosting-007:~/srv/runner/software/44fe7dd3f13ecd100894c6368a35c055/parts/pygolang-dev$ gpython -m pytest -vsx -k relpath ================================================= test session starts ================================================== platform linux2 -- Python 2.7.18, pytest-4.6.11, py-1.9.0, pluggy-0.13.1 -- /srv/slapgrid/slappart34/srv/runner/software/44fe7dd3f13ecd100894c6368a35c055/bin/gpython cachedir: .pytest_cache rootdir: /srv/slapgrid/slappart34/srv/runner/software/44fe7dd3f13ecd100894c6368a35c055/parts/pygolang-dev collected 115 items / 114 deselected / 1 selected gpython/gpython_test.py::test_pymain_run_via_relpath Traceback (most recent call last): File "./__init__.py", line 511, in <module> main() File "./__init__.py", line 395, in main pymain(argv, init) File "./__init__.py", line 217, in pymain init() File "./__init__.py", line 364, in init import gevent ImportError: No module named gevent FAILED ======================================================= FAILURES ======================================================= _____________________________________________ test_pymain_run_via_relpath ______________________________________________ @gpython_only def test_pymain_run_via_relpath(): argv = ['-c', 'import sys; print(sys.version)'] out1 = pyout( argv, pyexe=sys.executable) > out2 = pyout(['./__init__.py'] + argv, pyexe=sys._gpy_underlying_executable, cwd=here) gpython/gpython_test.py:301: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ golang/golang_test.py:1842: in pyout return pyrun(argv, stdin=stdin, stdout=stdout, stderr=stderr, **kw) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ argv = ['./__init__.py', '-c', 'import sys; print(sys.version)'], stdin = None, stdout = '', stderr = None kw = {'cwd': '/srv/slapgrid/slappart34/srv/runner/software/44fe7dd3f13ecd100894c6368a35c055/parts/pygolang-dev/gpython', 'pyexe': '/srv/slapgrid/slappart34/srv//runner//shared/python2.7/44578f5389723084b136574c34bc8023/bin/python2.7'} retcode = 1 def pyrun(argv, stdin=None, stdout=None, stderr=None, **kw): retcode, stdout, stderr = _pyrun(argv, stdin=stdin, stdout=stdout, stderr=stderr, **kw) if retcode: > raise RuntimeError(' '.join(argv) + '\n' + (stderr and str(stderr) or '(failed)')) E RuntimeError: ./__init__.py -c import sys; print(sys.version) E (failed) golang/golang_test.py:1836: RuntimeError ======================================= 1 failed, 114 deselected in 0.73 seconds ======================================= Fixes 076cdd8f (gpython: Fix crash when invoked as e.g. ./bin/gpython). /reviewed-on nexedi/pygolang!11
-
- 06 Dec, 2020 1 commit
-
-
Kirill Smelkov authored
PyPy changed home from bitbucket to foss.heptapod recently[1]. The old links on bitbucket no longer works. -> Update bitbucket links to their correspondents on foss.heptapod.net . [1] https://morepypy.blogspot.com/2020/02/pypy-and-cffi-have-moved-to-heptapod.html
-
- 02 Dec, 2020 2 commits
-
-
Kirill Smelkov authored
-
Kirill Smelkov authored
PyPy2 implements -OO differently compared to CPython and it breaks zope.interfaces at import time: https://foss.heptapod.net/pypy/pypy/-/issues/3356.
-
- 01 Dec, 2020 1 commit
-
-
Kirill Smelkov authored
- Teach gpython and pymain about `-`; - Teach gpython and pymain about -O; - Teach pymain to always set sys.executable, so that subprocess and multiprocessing work correctly out of the box; - Fix ./bin/gpython crash; - Rewrite options parsing via incremental getopt-like thing. This changes are motivated by nexedi/slapos!862 (comment 121470) and nexedi/slapos!862 (comment 121600). /reviewed-by @jerome /reviewed-on nexedi/pygolang!10
-
- 27 Nov, 2020 1 commit
-
-
Kirill Smelkov authored
Reported by @jerome here: nexedi/slapos!862 (comment 121738)
-
- 26 Nov, 2020 7 commits
-
-
Kirill Smelkov authored
Let's teach gpython and pymain about -O because buildout calls `python -O` to byte-compile python sources in optimized mode: nexedi/slapos!862 (comment 121470) When seeing -O, we take the approach to reexecute underlying python with -O, so that e.g. gpython -O file.py becomes executed as python -O gpython file.py This ensures correctness. The same approach could be used to implement support for e.g. -S and other low-level options. An earlier attempt to implement -O without reexecution can be seen at nexedi/pygolang!7 together with list of problems that arise via that way. Original idea to reexecute itself come from @jerome.
-
Kirill Smelkov authored
There was an assert because sys.path[0] is setup as realpath (real, not relative), but if exe was given as relative path it was raising like below: gpython/gpython_test.py::test_pymain_run_via_relpath GPython 0.0.7.post1 [gevent 20.9.0] / CPython 3.8.6 Traceback (most recent call last): File "./__init__.py", line 482, in <module> main() File "./__init__.py", line 366, in main pymain(argv, init) File "./__init__.py", line 82, in pymain raise RuntimeError('pymain: internal error: sys.path[0] was not set by underlying python to dirname(exe):' RuntimeError: pymain: internal error: sys.path[0] was not set by underlying python to dirname(exe): exe: ./__init__.py sys.path[0]: /home/kirr/src/tools/go/pygolang-master/gpython FAILED -> Fix it by setting up exe as realpath.
-
Kirill Smelkov authored
This is generic step and should be done under both gpython and standard python. Handling sys.executable inside pymain offloads users from setting up sys.executable by themselves, e.g. as in here: https://lab.nexedi.com/nexedi/slapos/blob/46ed2afe/component/pygolang/buildout.cfg#L39-43
-
Kirill Smelkov authored
In other words pymain now requires to be passed in full argv when called. We will need exe=argv[0] in a followup patch to be able to reexecute underlying python interpreter when given options like -O, -S, etc... This is change in behaviour and in general must come with changing pymain name - e.g. to pymain2 - so that old pymain continues to work as before. However since there are not so many known pymain users, we can probably do this change in place since we'll care ourselves about all those users: - [python-interpreter] in SlapOS: https://lab.nexedi.com/nexedi/slapos/blob/46ed2afe/component/pygolang/buildout.cfg#L30-53 - pyruntraced in Go123: https://lab.nexedi.com/kirr/go123/blob/96046edf/tracing/cmd/pyruntraced - zobjtrace in ZODB: https://lab.nexedi.com/kirr/ZODB/blob/07c21671/zobjtrace So I hope it is ok.
-
Kirill Smelkov authored
Python allows multiple single-letter options and their arguments to be coming on single argument, for example: python -OQc'print 1' python -OQc 'print 1' python -OQ -c 'print 1' etc... We are currently trying to handle that at every option, but even though it kind of works, it is limited and will break once we will start adding options. -> Refactor options parsing into getopt-style helper. We cannot use getopt itself because it will complain e.g. on `gpython file.py --my-custom-opt` that my-custom-opt is unexpected.
-
Jérome Perrin authored
follow up of 64088e8a (gpython: Don't inherit __future__ when executing scripts, 2020-11-26) /reviewed-by @kirr /reviewed-on nexedi/pygolang!9
-
Jérome Perrin authored
Because we use execfile to emulate "python script.py" invocation, future enabled in gpython main script are also enabled in "script.py" and because we use print_function feature, it was also applied in "script.py". On python 2 when "script.py" uses print statements they were considered as SyntaxError. Hopefuly, compile has a `dont_inherit` flag which does exactly what we need here: compile the script without inheriting futures enabled, so we use compile with this flag. /reviewed-by @kirr /reviewed-on nexedi/pygolang!8
-
- 02 Nov, 2020 1 commit
-
-
Kirill Smelkov authored
Nxdtest[1] is tox-like tool to run tests under Nexedi testing infrastructure. [1] https://lab.nexedi.com/nexedi/nxdtest /cc @jerome, @gabriel /reviewed-on nexedi/pygolang!6
-
- 14 Oct, 2020 5 commits
-
-
Kirill Smelkov authored
-
Kirill Smelkov authored
With buildout raw underlying python interpreter does not have access to eggs with which gpython script was generated. See previous commit for details.
-
Kirill Smelkov authored
When buildout generates scripts, it injects the following prologue for them: #!/path/to/underlying/python import sys sys.path[0:0] = [ 'path/to/egg1', 'path/to/egg2', ... ] import SCRIPT SCRIPT.main() This way path to eggs are inserted before what was originally sys.path[0] and it breaks gpython check that `sys.path[0] == exedir`. -> Fix it by detecting buildout and in that case verifying that exedir is only present in sys.path, not located in sys.path[0].
-
Kirill Smelkov authored
We were appending whole $PYTHONPATH to pathv - a vector of path. But we should instead first split $PYTHONPATH by pathsep and extend pathv.
-
Kirill Smelkov authored
It is os.pathsep, which is ';' on Windows.
-
- 22 Sep, 2020 6 commits
-
-
Kirill Smelkov authored
-
Kirill Smelkov authored
This continues 6b4990f6 (gpython: Fix pymain to properly setup sys.path[0]) - now we add test and verify that gpython actually sets sys.path in the same way as underlying python does: - don't add directory of gpython to sys.path - for `gpython file` add to sys.path[0] realpath(dir(file)) instead of dir(file) (see PySys_SetArgvEx for the reference, and else - if we do not make this change - added test breaks) - for `gpython -m mod` add to sys.path[0] realpath('') instead of ''. This exactly matches PY3 behaviour, while PY2 behaviour is to add '' However PY3 behaviour * is more sane, and * fixes test_defer_excchain_traceback on py27-gevent -> so we stick to it. Now all tests pass again.
-
Kirill Smelkov authored
GPython preimports golang and gevent, but until now, it was preimporting them before adjusting sys.path according to given -c/-m/file/... And this was leading to difference in behaviour with standard Python where e.g. `import golang` might result in different files loaded if e.g. golang/ is there on cwd and -c/-m was given. -> Rework gpython startup so that golang/gevent preimport happens after sys.path initialization. This fixes `tox -e py27-gevent` which was previously failing on test_defer_excchain_dump_pytest / test_defer_excchain_dump_ipython because dumped paths were different than expected ones. It, however, breaks test_defer_excchain_traceback on py27-gevent. -> We'll do more fixups to sys.path handling in the next patch which will fix that failure as well.
-
Kirill Smelkov authored
We'll need this in the next patch, where golang/gevent preimport will be moved from "before call to pymain", to "in between option parsing and interpreter start".
-
Kirill Smelkov authored
There, with pytest 6.0.x, pytest emits its internal deprecation warning (the usage of deprecated attribute is inside pytest): .../_pytest/compat.py:340: PytestDeprecationWarning: The TerminalReporter.writer attribute is deprecated, use TerminalReporter._tw instead at your own risk. See https://docs.pytest.org/en/stable/deprecations.html#terminalreporter-writer for more information. return getattr(object, name, default) Which leads to test_defer_excchain_dump_pytest failure since this test verifies stderr to be empty: def test_defer_excchain_dump_pytest(): tbok = readfile(dir_testprog + "/golang_test_defer_excchain.txt-pytest") retcode, stdout, stderr = _pyrun(["-m", "pytest", "-o", "python_functions=main", "--tb=short", "golang_test_defer_excchain.py"], cwd=dir_testprog, stdout=PIPE, stderr=PIPE) assert retcode != 0, (stdout, stderr) > assert stderr == b"" E AssertionError: assert b'/home/kirr/...e, default)\n' == b'' E Full diff: E - b'' E + ( E + b'/home/kirr/src/tools/go/py3dbg.venv/lib/python3.8/site-packages/_pytest/comp' E + b'at.py:340: PytestDeprecationWarning: The TerminalReporter.writer attribute i' E + b's deprecated, use TerminalReporter._tw instead at your own risk.\nSee htt' E + b'ps://docs.pytest.org/en/stable/deprecations.html#terminalreporter-writer for'... E E ...Full output truncated (3 lines hidden), use '-vv' to show -> Fix it by not letting spawned pytest to emit deprecation warnings in that test.
-
Kirill Smelkov authored
We'll need warnings control in the next patch to fix Pytest integration wrt python3-dbg with `-W ignore::DeprecationWarning`.
-
- 21 Sep, 2020 1 commit
-
-
Kirill Smelkov authored
Reorganize parsiong of command line options so that first all options are parsed in a loop, and only after that a module/file/command is executed. This is needed as preparatory step for next patch: there we'll add support for -W, and `-W arg` can be given multiple times and has to be processed multiple times by creating multiple corresponding warning filters. Because those warning filters has to be applied uniformly to all 4 codepaths of execution phase (-m/-c/file/interactive console), it makes sense to move execution phase to after options parsing and inject common runtime preparatory steps right before that. This logic generally applies not only to -W, but to all other python options - e.g. -Q,-u,...
-
- 18 Sep, 2020 3 commits
-
-
Kirill Smelkov authored
Current Debian testing builds libtsan/libasan with --as-needed, which leads to libstdc++.so not being linked in. Compare Debian 10: kirr@link:~/src/tools/go/pygolang$ ldd /usr/lib/x86_64-linux-gnu/libasan.so.5 linux-vdso.so.1 (0x00007ffe7e97f000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f00ad9d5000) librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f00ad9cb000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f00ad9aa000) libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f00ad7d2000) <-- libstdc++ libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f00ad64f000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f00ad48e000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f00ad472000) /lib64/ld-linux-x86-64.so.2 (0x00007f00ae7c8000) to Debian testing: kirr@deco:~/src/tools/go/pygolang$ ldd /usr/lib/x86_64-linux-gnu/libasan.so.6 linux-vdso.so.1 (0x00007ffe15d0b000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f53ba069000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f53ba047000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f53b9f03000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f53b9d3e000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f53b9d24000) /lib64/ld-linux-x86-64.so.2 (0x00007f53baa6b000) where libstdc++ is not being linked to libasan.so This leads to the following crash: golang/golang_test.py::test_pyx_select_inplace ==181237==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_interceptors.cpp:333 "((__interception::real___cxa_throw)) != (0)" (0x0, 0x0) #0 0x7f76aa9f4657 (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xb2657) #1 0x7f76aaa11d5a (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xcfd5a) #2 0x7f76aa97ac14 in __cxa_throw (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x38c14) #3 0x7f76a497a9ef in panic golang/runtime/libgolang.cpp:79 #4 0x7f76a497e9a9 in _chanselect golang/runtime/libgolang.cpp:956 #5 0x7f76a455573c in select<1> golang/libgolang.h:535 #6 0x7f76a454fa4b in _test_select_inplace() golang/runtime/libgolang_test.cpp:417 #7 0x7f76a452781e in __pyx_pf_6golang_12_golang_test_30test_select_inplace golang/_golang_test.cpp:4859 #8 0x7f76a45277db in __pyx_pw_6golang_12_golang_test_31test_select_inplace golang/_golang_test.cpp:4821 explained by https://github.com/google/sanitizers/issues/934 -> work it around by manually preloading libstdc++ as well.
-
Kirill Smelkov authored
test_defer_excchain_dump_ipython was setting fresh env anew, thus not propagating $LD_PRELOAD that trun sets when libgolang was built with thread or address sanitizer. This way the subprocess spawned for IPython was failing with e.g. ==152924==ASan runtime does not come first in initial library list; you should either link runtime to your application or manually preload it with LD_PRELOAD. -> Fix it by only adjusting environment for spawned IPython instead of setting it completely anew. Fixes: 09629367 (golang: tests: Add tests for IPython and Pytest integration patches)
-
Kirill Smelkov authored
For example if test_defer_excchain_dump_ipython fails the output was: > assert retcode == 0 E assert 1 == 0 and it was unclear what was going on. Now the output is e.g. > assert retcode == 0, (stdout, stderr) E AssertionError: ('', '==152924==ASan runtime does not come first in initial library list; you should either link runtime to your application or manually preload it with LD_PRELOAD. E ') E assert 1 == 0 Amends: 09629367 (golang: tests: Add tests for IPython and Pytest integration patches) and bb9a94c3 (golang: Teach defer to chain exceptions (PEP 3134) even on Python2).
-
- 30 Jul, 2020 3 commits
-
-
Kirill Smelkov authored
Until now gpython was always activating gevent on startup + adding goodness such as "always UTF-8 default encoding"; go, chan, b/u and friends available from builtin namespace, etc... While those goodness are sometimes useful on their own, it is not always appropriate to force a project to switch from threads to gevent. For this reason add a flag to select which runtime - either gevent or threads - gpython should use. gpython -Xgpython.runtime=gevent selects gevent, while gpython -Xgpython.runtime=threads selects threads. Gevent remains the default. It is also possible to specify desired runtime via $GPYTHON_RUNTIME environment variable. /reviewed-on nexedi/pygolang!5
-
Kirill Smelkov authored
Restructure code a bit to prepare it for the next patch. Plain code movement. /reviewed-on !5
-
Kirill Smelkov authored
On CPython: $ python -V Python 2.7.18 $ gpython -V GPython 0.0.6.post2 [gevent 20.6.2] / CPython 2.7.18 On PyPy: $ python -V Python 3.6.9 (2ad108f17bdb, Apr 07 2020, 02:59:05) [PyPy 7.3.1 with GCC 7.3.1 20180303 (Red Hat 7.3.1-5)] $ gpython -V GPython 0.0.6.post2 [gevent 20.5.0] / PyPy 7.3.1 / Python 3.6.9 /reviewed-on !5
-