From a1417cb30553f02dafd9aa23a1fae11d9c54d686 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Mon, 23 Sep 2024 15:43:46 -0400 Subject: [PATCH] feat(pymongo): add support for v4.9 (#10718) ## Motivation - Add and test support for pymongo v4.9.1 ## Reproduction ``` pip install ddtrace==2.11.6 pymongo==4.9.1 python -c "import ddtrace; ddtrace.patch_all(pymongo=True); import pymongo" ``` ## Output ``` File "dd-trace-py/ddtrace/_monkey.py", line 165, in on_import imported_module = importlib.import_module(path) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/munirabdinur/.pyenv/versions/3.12.2/lib/python3.12/importlib/__init__.py", line 90, in import_module return _bootstrap._gcd_import(name[level:], package, level) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "", line 1387, in _gcd_import File "", line 1360, in _find_and_load File "", line 1331, in _find_and_load_unlocked File "", line 935, in _load_unlocked File "dd-trace-py/ddtrace/internal/module.py", line 309, in _exec_module self.loader.exec_module(module) File "", line 995, in exec_module File "", line 488, in _call_with_frames_removed File "dd-trace-py/ddtrace/contrib/pymongo/__init__.py", line 45, in from .patch import get_version File "", line 1360, in _find_and_load File "", line 1331, in _find_and_load_unlocked File "", line 935, in _load_unlocked File "dd-trace-py/ddtrace/internal/module.py", line 309, in _exec_module self.loader.exec_module(module) File "dd-trace-py/ddtrace/contrib/pymongo/patch.py", line 51, in _VERIFY_VERSION_CLASS = pymongo.pool.SocketInfo if _VERSION < (4, 5) else pymongo.pool.Connection ``` ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Quinna Halim --- .../requirements/{13fc59d.txt => 10a00e7.txt} | 6 +- .../requirements/{63c9090.txt => 1424e42.txt} | 6 +- .../requirements/{9627328.txt => 14e85f3.txt} | 6 +- .../requirements/{c998f1e.txt => 16cae33.txt} | 4 +- .../requirements/{14fb6c4.txt => 1ce9a99.txt} | 10 ++-- .../requirements/{16d0967.txt => 1ed6ce0.txt} | 4 +- .../requirements/{f97f8c0.txt => 328b28c.txt} | 6 +- .../requirements/{182cbd0.txt => ad40916.txt} | 6 +- .../requirements/{541b4d9.txt => b089663.txt} | 10 ++-- .../requirements/{172570f.txt => b344fed.txt} | 6 +- .../requirements/{1dad05b.txt => de53117.txt} | 10 ++-- .../requirements/{178ae92.txt => f9d0e8e.txt} | 10 ++-- ddtrace/contrib/internal/pymongo/patch.py | 55 +++++++++++-------- .../pymong-4-9-support-83f7c613e5e009e6.yaml | 4 ++ riotfile.py | 3 +- 15 files changed, 80 insertions(+), 66 deletions(-) rename .riot/requirements/{13fc59d.txt => 10a00e7.txt} (76%) rename .riot/requirements/{63c9090.txt => 1424e42.txt} (75%) rename .riot/requirements/{9627328.txt => 14e85f3.txt} (74%) rename .riot/requirements/{c998f1e.txt => 16cae33.txt} (76%) rename .riot/requirements/{14fb6c4.txt => 1ce9a99.txt} (71%) rename .riot/requirements/{16d0967.txt => 1ed6ce0.txt} (76%) rename .riot/requirements/{f97f8c0.txt => 328b28c.txt} (76%) rename .riot/requirements/{182cbd0.txt => ad40916.txt} (74%) rename .riot/requirements/{541b4d9.txt => b089663.txt} (70%) rename .riot/requirements/{172570f.txt => b344fed.txt} (75%) rename .riot/requirements/{1dad05b.txt => de53117.txt} (70%) rename .riot/requirements/{178ae92.txt => f9d0e8e.txt} (71%) create mode 100644 releasenotes/notes/pymong-4-9-support-83f7c613e5e009e6.yaml diff --git a/.riot/requirements/13fc59d.txt b/.riot/requirements/10a00e7.txt similarity index 76% rename from .riot/requirements/13fc59d.txt rename to .riot/requirements/10a00e7.txt index 30ecfd08a15..ed2fd846015 100644 --- a/.riot/requirements/13fc59d.txt +++ b/.riot/requirements/10a00e7.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/13fc59d.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/10a00e7.in # attrs==24.2.0 coverage[toml]==7.6.1 @@ -11,12 +11,12 @@ exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 -mongoengine==0.28.2 +mongoengine==0.29.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 pymongo==4.8.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 diff --git a/.riot/requirements/63c9090.txt b/.riot/requirements/1424e42.txt similarity index 75% rename from .riot/requirements/63c9090.txt rename to .riot/requirements/1424e42.txt index 2d287e7aa2f..f58bbb22bd6 100644 --- a/.riot/requirements/63c9090.txt +++ b/.riot/requirements/1424e42.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/63c9090.in +# pip-compile --no-annotate .riot/requirements/1424e42.in # attrs==24.2.0 coverage[toml]==7.6.1 @@ -10,12 +10,12 @@ dnspython==2.6.1 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 -mongoengine==0.28.2 +mongoengine==0.29.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 pymongo==4.8.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 diff --git a/.riot/requirements/9627328.txt b/.riot/requirements/14e85f3.txt similarity index 74% rename from .riot/requirements/9627328.txt rename to .riot/requirements/14e85f3.txt index 68796df6809..44ce4a54256 100644 --- a/.riot/requirements/9627328.txt +++ b/.riot/requirements/14e85f3.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/9627328.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/14e85f3.in # attrs==24.2.0 coverage[toml]==7.6.1 @@ -10,12 +10,12 @@ dnspython==2.6.1 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 -mongoengine==0.28.2 +mongoengine==0.29.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 pymongo==4.8.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 diff --git a/.riot/requirements/c998f1e.txt b/.riot/requirements/16cae33.txt similarity index 76% rename from .riot/requirements/c998f1e.txt rename to .riot/requirements/16cae33.txt index 9c41810e156..0df83f49b08 100644 --- a/.riot/requirements/c998f1e.txt +++ b/.riot/requirements/16cae33.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/c998f1e.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/16cae33.in # attrs==24.2.0 coverage[toml]==7.2.7 @@ -12,7 +12,7 @@ hypothesis==6.45.0 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 -mongoengine==0.28.2 +mongoengine==0.29.1 opentracing==2.4.0 packaging==24.0 pluggy==1.2.0 diff --git a/.riot/requirements/14fb6c4.txt b/.riot/requirements/1ce9a99.txt similarity index 71% rename from .riot/requirements/14fb6c4.txt rename to .riot/requirements/1ce9a99.txt index 7a8c4a1a4e1..3d8f032cfab 100644 --- a/.riot/requirements/14fb6c4.txt +++ b/.riot/requirements/1ce9a99.txt @@ -2,25 +2,25 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/14fb6c4.in +# pip-compile --no-annotate .riot/requirements/1ce9a99.in # attrs==24.2.0 coverage[toml]==7.6.1 dnspython==2.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 -mongoengine==0.28.2 +mongoengine==0.29.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 pymongo==4.8.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 tomli==2.0.1 -zipp==3.20.1 +zipp==3.20.2 diff --git a/.riot/requirements/16d0967.txt b/.riot/requirements/1ed6ce0.txt similarity index 76% rename from .riot/requirements/16d0967.txt rename to .riot/requirements/1ed6ce0.txt index ac6b8992c58..a51ab00415d 100644 --- a/.riot/requirements/16d0967.txt +++ b/.riot/requirements/1ed6ce0.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/16d0967.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/1ed6ce0.in # attrs==24.2.0 coverage[toml]==7.2.7 @@ -12,7 +12,7 @@ hypothesis==6.45.0 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 -mongoengine==0.28.2 +mongoengine==0.29.1 opentracing==2.4.0 packaging==24.0 pluggy==1.2.0 diff --git a/.riot/requirements/f97f8c0.txt b/.riot/requirements/328b28c.txt similarity index 76% rename from .riot/requirements/f97f8c0.txt rename to .riot/requirements/328b28c.txt index 771c4dbb4be..38eac9651b9 100644 --- a/.riot/requirements/f97f8c0.txt +++ b/.riot/requirements/328b28c.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/f97f8c0.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/328b28c.in # attrs==24.2.0 coverage[toml]==7.6.1 @@ -11,12 +11,12 @@ exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 -mongoengine==0.28.2 +mongoengine==0.29.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 pymongo==4.8.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 diff --git a/.riot/requirements/182cbd0.txt b/.riot/requirements/ad40916.txt similarity index 74% rename from .riot/requirements/182cbd0.txt rename to .riot/requirements/ad40916.txt index b7b023b5946..853f497ee9e 100644 --- a/.riot/requirements/182cbd0.txt +++ b/.riot/requirements/ad40916.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/182cbd0.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/ad40916.in # attrs==24.2.0 coverage[toml]==7.6.1 @@ -10,12 +10,12 @@ dnspython==2.6.1 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 -mongoengine==0.28.2 +mongoengine==0.29.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 pymongo==4.8.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 diff --git a/.riot/requirements/541b4d9.txt b/.riot/requirements/b089663.txt similarity index 70% rename from .riot/requirements/541b4d9.txt rename to .riot/requirements/b089663.txt index 23c5ae20a2a..956c6d73e92 100644 --- a/.riot/requirements/541b4d9.txt +++ b/.riot/requirements/b089663.txt @@ -2,25 +2,25 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/541b4d9.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/b089663.in # attrs==24.2.0 coverage[toml]==7.6.1 dnspython==2.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 -mongoengine==0.28.2 +mongoengine==0.29.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 pymongo==4.8.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 tomli==2.0.1 -zipp==3.20.1 +zipp==3.20.2 diff --git a/.riot/requirements/172570f.txt b/.riot/requirements/b344fed.txt similarity index 75% rename from .riot/requirements/172570f.txt rename to .riot/requirements/b344fed.txt index 929d1339b8f..73e61eb69f9 100644 --- a/.riot/requirements/172570f.txt +++ b/.riot/requirements/b344fed.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/172570f.in +# pip-compile --no-annotate .riot/requirements/b344fed.in # attrs==24.2.0 coverage[toml]==7.6.1 @@ -10,12 +10,12 @@ dnspython==2.6.1 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 -mongoengine==0.28.2 +mongoengine==0.29.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 pymongo==4.8.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 diff --git a/.riot/requirements/1dad05b.txt b/.riot/requirements/de53117.txt similarity index 70% rename from .riot/requirements/1dad05b.txt rename to .riot/requirements/de53117.txt index 1c0fe4ed4c1..1dd3dcf18f2 100644 --- a/.riot/requirements/1dad05b.txt +++ b/.riot/requirements/de53117.txt @@ -2,25 +2,25 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1dad05b.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/de53117.in # attrs==24.2.0 coverage[toml]==7.6.1 dnspython==2.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 -mongoengine==0.28.2 +mongoengine==0.29.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 pymongo==4.8.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 tomli==2.0.1 -zipp==3.20.1 +zipp==3.20.2 diff --git a/.riot/requirements/178ae92.txt b/.riot/requirements/f9d0e8e.txt similarity index 71% rename from .riot/requirements/178ae92.txt rename to .riot/requirements/f9d0e8e.txt index 7963ea0e1db..42bc8937d56 100644 --- a/.riot/requirements/178ae92.txt +++ b/.riot/requirements/f9d0e8e.txt @@ -2,25 +2,25 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/178ae92.in +# pip-compile --no-annotate .riot/requirements/f9d0e8e.in # attrs==24.2.0 coverage[toml]==7.6.1 dnspython==2.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 -mongoengine==0.28.2 +mongoengine==0.29.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 pymongo==4.8.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 tomli==2.0.1 -zipp==3.20.1 +zipp==3.20.2 diff --git a/ddtrace/contrib/internal/pymongo/patch.py b/ddtrace/contrib/internal/pymongo/patch.py index e40fba1b2c5..5032ce3b48a 100644 --- a/ddtrace/contrib/internal/pymongo/patch.py +++ b/ddtrace/contrib/internal/pymongo/patch.py @@ -27,6 +27,22 @@ from .client import set_address_tags +_VERSION = pymongo.version_tuple + +if _VERSION >= (4, 9): + from pymongo.synchronous.pool import Connection + from pymongo.synchronous.server import Server + from pymongo.synchronous.topology import Topology +elif _VERSION >= (4, 5): + from pymongo.pool import Connection + from pymongo.server import Server + from pymongo.topology import Topology +else: + from pymongo.pool import SocketInfo as Connection + from pymongo.server import Server + from pymongo.topology import Topology + + _CHECKOUT_FN_NAME = "get_socket" if pymongo.version_tuple < (4, 5) else "checkout" @@ -41,9 +57,6 @@ def get_version(): return getattr(pymongo, "__version__", "") -_VERSION = pymongo.version_tuple - - def patch(): if getattr(pymongo, "_datadog_patch", False): return @@ -60,43 +73,39 @@ def unpatch(): def patch_pymongo_module(): _w(pymongo.MongoClient.__init__, _trace_mongo_client_init) - _w(pymongo.topology.Topology.select_server, _trace_topology_select_server) + _w(Topology.select_server, _trace_topology_select_server) if _VERSION >= (3, 12): - _w(pymongo.server.Server.run_operation, _trace_server_run_operation_and_with_response) + _w(Server.run_operation, _trace_server_run_operation_and_with_response) elif _VERSION >= (3, 9): - _w(pymongo.server.Server.run_operation_with_response, _trace_server_run_operation_and_with_response) + _w(Server.run_operation_with_response, _trace_server_run_operation_and_with_response) else: - _w(pymongo.server.Server.send_message_with_response, _trace_server_send_message_with_response) + _w(Server.send_message_with_response, _trace_server_send_message_with_response) if _VERSION >= (4, 5): - _w(pymongo.server.Server.checkout, traced_get_socket) - _w(pymongo.pool.Connection.command, _trace_socket_command) - _w(pymongo.pool.Connection.write_command, _trace_socket_write_command) + _w(Server.checkout, traced_get_socket) else: - _w(pymongo.server.Server.get_socket, traced_get_socket) - _w(pymongo.pool.SocketInfo.command, _trace_socket_command) - _w(pymongo.pool.SocketInfo.write_command, _trace_socket_write_command) + _w(Server.get_socket, traced_get_socket) + _w(Connection.command, _trace_socket_command) + _w(Connection.write_command, _trace_socket_write_command) def unpatch_pymongo_module(): _u(pymongo.MongoClient.__init__, _trace_mongo_client_init) - _u(pymongo.topology.Topology.select_server, _trace_topology_select_server) + _u(Topology.select_server, _trace_topology_select_server) if _VERSION >= (3, 12): - _u(pymongo.server.Server.run_operation, _trace_server_run_operation_and_with_response) + _u(Server.run_operation, _trace_server_run_operation_and_with_response) elif _VERSION >= (3, 9): - _u(pymongo.server.Server.run_operation_with_response, _trace_server_run_operation_and_with_response) + _u(Server.run_operation_with_response, _trace_server_run_operation_and_with_response) else: - _u(pymongo.server.Server.send_message_with_response, _trace_server_send_message_with_response) + _u(Server.send_message_with_response, _trace_server_send_message_with_response) if _VERSION >= (4, 5): - _u(pymongo.server.Server.checkout, traced_get_socket) - _u(pymongo.pool.Connection.command, _trace_socket_command) - _u(pymongo.pool.Connection.write_command, _trace_socket_write_command) + _u(Server.checkout, traced_get_socket) else: - _u(pymongo.server.Server.get_socket, traced_get_socket) - _u(pymongo.pool.SocketInfo.command, _trace_socket_command) - _u(pymongo.pool.SocketInfo.write_command, _trace_socket_write_command) + _u(Server.get_socket, traced_get_socket) + _u(Connection.command, _trace_socket_command) + _u(Connection.write_command, _trace_socket_write_command) @contextlib.contextmanager diff --git a/releasenotes/notes/pymong-4-9-support-83f7c613e5e009e6.yaml b/releasenotes/notes/pymong-4-9-support-83f7c613e5e009e6.yaml new file mode 100644 index 00000000000..613885e7ee1 --- /dev/null +++ b/releasenotes/notes/pymong-4-9-support-83f7c613e5e009e6.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + pymongo: Adds support for pymongo>=4.9.0 diff --git a/riotfile.py b/riotfile.py index 5a3cd2174f8..2d02d930b57 100644 --- a/riotfile.py +++ b/riotfile.py @@ -1434,7 +1434,8 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): name="mongoengine", command="pytest {cmdargs} tests/contrib/mongoengine", pkgs={ - "pymongo": latest, + # pymongo v4.9.0 introduced breaking changes that are not yet supported by mongoengine + "pymongo": "<4.9.0", "pytest-randomly": latest, }, venvs=[