From: Mark Beierl Date: Wed, 10 May 2023 19:43:03 +0000 (-0400) Subject: Update to Python 3.10 and Ubuntu 22.04 X-Git-Tag: release-v14.0-start~2 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=refs%2Fchanges%2F67%2F13367%2F1;p=osm%2FMON.git Update to Python 3.10 and Ubuntu 22.04 Removal of deprecated event loop Updated pip requirements Change-Id: I96a895d8f9fa8db89e6bbfd6f009d4f56deaa418 Signed-off-by: Mark Beierl --- diff --git a/Dockerfile b/Dockerfile index c597522..27ab273 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ # devops-stages/stage-build.sh # -FROM ubuntu:20.04 +FROM ubuntu:22.04 ARG APT_PROXY RUN if [ ! -z $APT_PROXY ] ; then \ @@ -37,7 +37,9 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ python3 \ python3-all \ python3-dev \ - python3-setuptools + python3-setuptools \ + python3-pip \ + tox -RUN python3 -m easy_install pip==21.3.1 -RUN pip install tox==3.24.5 +ENV LC_ALL C.UTF-8 +ENV LANG C.UTF-8 diff --git a/osm_mon/cmd/mon_server.py b/osm_mon/cmd/mon_server.py index 7d676d7..c4fa08b 100644 --- a/osm_mon/cmd/mon_server.py +++ b/osm_mon/cmd/mon_server.py @@ -22,7 +22,6 @@ # contact: bdiaz@whitestack.com or glavado@whitestack.com ## import argparse -import asyncio import logging import sys @@ -52,9 +51,8 @@ def main(): log.info("Starting MON Server...") log.debug("Config: %s", cfg.conf) log.info("Initializing database...") - loop = asyncio.get_event_loop() try: - server = Server(cfg, loop) + server = Server(cfg) server.run() except Exception as e: log.error("Failed to start MON Server") diff --git a/osm_mon/collector/vnf_collectors/juju.py b/osm_mon/collector/vnf_collectors/juju.py index cb924a6..fbc6bc2 100644 --- a/osm_mon/collector/vnf_collectors/juju.py +++ b/osm_mon/collector/vnf_collectors/juju.py @@ -39,7 +39,6 @@ class VCACollector(BaseCollector): def __init__(self, config: Config): super().__init__(config) self.common_db = CommonDbClient(config) - self.loop = asyncio.get_event_loop() # host = config.get("vca", "host") # port = config.get("vca", "port") if "port" in config.conf["vca"] else 17070 @@ -60,7 +59,6 @@ class VCACollector(BaseCollector): db=self.common_db.common_db, fs=object(), log=log, - loop=self.loop, on_update_db=None, ) @@ -110,7 +108,7 @@ class VCACollector(BaseCollector): if vca_deployment_info.get("model") and vca_deployment_info.get( "application" ): - measures = self.loop.run_until_complete( + measures = asyncio.run( self.n2vc.get_metrics( vca_deployment_info["model"], vca_deployment_info["application"], diff --git a/osm_mon/core/message_bus_client.py b/osm_mon/core/message_bus_client.py index 7d194c3..8eee83a 100644 --- a/osm_mon/core/message_bus_client.py +++ b/osm_mon/core/message_bus_client.py @@ -21,7 +21,6 @@ # For those usages not covered by the Apache License, Version 2.0 please # contact: bdiaz@whitestack.com or glavado@whitestack.com ## -import asyncio from typing import List, Callable from osm_common import msgkafka, msglocal @@ -30,7 +29,7 @@ from osm_mon.core.config import Config class MessageBusClient: - def __init__(self, config: Config, loop=None): + def __init__(self, config: Config): if config.get("message", "driver") == "local": self.msg_bus = msglocal.MsgLocal() elif config.get("message", "driver") == "kafka": @@ -40,9 +39,6 @@ class MessageBusClient: "Unknown message bug driver {}".format(config.get("section", "driver")) ) self.msg_bus.connect(config.get("message")) - if not loop: - loop = asyncio.get_event_loop() - self.loop = loop async def aioread(self, topics: List[str], callback: Callable = None, **kwargs): """ @@ -52,7 +48,7 @@ class MessageBusClient: :param kwargs: Keyword arguments to be passed to callback function. :return: None """ - await self.msg_bus.aioread(topics, self.loop, aiocallback=callback, **kwargs) + await self.msg_bus.aioread(topics, aiocallback=callback, **kwargs) async def aiowrite(self, topic: str, key: str, msg: dict): """ @@ -62,7 +58,7 @@ class MessageBusClient: :param msg: Dictionary containing message to be written. :return: None """ - await self.msg_bus.aiowrite(topic, key, msg, self.loop) + await self.msg_bus.aiowrite(topic, key, msg) async def aioread_once(self, topic: str): """ @@ -70,5 +66,5 @@ class MessageBusClient: :param topic: topic to retrieve message from. :return: tuple(topic, key, message) """ - result = await self.msg_bus.aioread(topic, self.loop) + result = await self.msg_bus.aioread(topic) return result diff --git a/osm_mon/dashboarder/dashboarder.py b/osm_mon/dashboarder/dashboarder.py index f3bd97e..1d523cf 100644 --- a/osm_mon/dashboarder/dashboarder.py +++ b/osm_mon/dashboarder/dashboarder.py @@ -35,17 +35,14 @@ log = logging.getLogger(__name__) class Dashboarder: - def __init__(self, config: Config, loop=None): + def __init__(self, config: Config): self.conf = config self.service = DashboarderService(config) - if not loop: - loop = asyncio.get_event_loop() - self.loop = loop self.msg_bus = MessageBusClient(config) # run consumer for grafana user management def run(self): - self.loop.run_until_complete(self.start()) + asyncio.run(self.start()) async def start(self, wait_time=5): topics = ["users", "project"] diff --git a/osm_mon/evaluator/evaluator.py b/osm_mon/evaluator/evaluator.py index 732f8ac..61b788a 100644 --- a/osm_mon/evaluator/evaluator.py +++ b/osm_mon/evaluator/evaluator.py @@ -35,11 +35,8 @@ log = logging.getLogger(__name__) class Evaluator: - def __init__(self, config: Config, loop=None): + def __init__(self, config: Config): self.conf = config - if not loop: - loop = asyncio.get_event_loop() - self.loop = loop self.service = EvaluatorService(config) self.msg_bus = MessageBusClient(config) @@ -69,7 +66,7 @@ class Evaluator: log.debug("_notify_alarm") resp_message = self._build_alarm_response(alarm, status) log.info("Sent alarm notification: %s", resp_message) - self.loop.run_until_complete( + asyncio.run( self.msg_bus.aiowrite("alarm_response", "notify_alarm", resp_message) ) evaluator_service = EvaluatorService(self.conf) diff --git a/osm_mon/server/server.py b/osm_mon/server/server.py index f05cdec..bb8f0e8 100755 --- a/osm_mon/server/server.py +++ b/osm_mon/server/server.py @@ -37,17 +37,14 @@ log = logging.getLogger(__name__) class Server: - def __init__(self, config: Config, loop=None): + def __init__(self, config: Config): self.conf = config - if not loop: - loop = asyncio.get_event_loop() - self.loop = loop self.msg_bus = MessageBusClient(config) self.service = ServerService(config) self.service.populate_prometheus() def run(self): - self.loop.run_until_complete(self.start()) + asyncio.run(self.start()) async def start(self, wait_time=5): topics = ["alarm_request"] diff --git a/osm_mon/tests/unit/core/test_message_bus_client.py b/osm_mon/tests/unit/core/test_message_bus_client.py index 126eb9f..b35c6c1 100644 --- a/osm_mon/tests/unit/core/test_message_bus_client.py +++ b/osm_mon/tests/unit/core/test_message_bus_client.py @@ -34,39 +34,38 @@ class TestMessageBusClient(TestCase): def setUp(self): self.config = Config() self.config.set("message", "driver", "kafka") - self.loop = asyncio.new_event_loop() @mock.patch.object(MsgKafka, "aioread") def test_aioread(self, aioread): async def mock_callback(): pass - future = asyncio.Future(loop=self.loop) + future = asyncio.Future(loop=asyncio.new_event_loop()) future.set_result("mock") aioread.return_value = future - msg_bus = MessageBusClient(self.config, loop=self.loop) + msg_bus = MessageBusClient(self.config) topic = "test_topic" - self.loop.run_until_complete(msg_bus.aioread([topic], mock_callback)) - aioread.assert_called_with(["test_topic"], self.loop, aiocallback=mock_callback) + asyncio.run(msg_bus.aioread([topic], mock_callback)) + aioread.assert_called_with(["test_topic"], aiocallback=mock_callback) @mock.patch.object(MsgKafka, "aiowrite") def test_aiowrite(self, aiowrite): - future = asyncio.Future(loop=self.loop) + future = asyncio.Future(loop=asyncio.new_event_loop()) future.set_result("mock") aiowrite.return_value = future - msg_bus = MessageBusClient(self.config, loop=self.loop) + msg_bus = MessageBusClient(self.config) topic = "test_topic" key = "test_key" msg = {"test": "test_msg"} - self.loop.run_until_complete(msg_bus.aiowrite(topic, key, msg)) - aiowrite.assert_called_with(topic, key, msg, self.loop) + asyncio.run(msg_bus.aiowrite(topic, key, msg)) + aiowrite.assert_called_with(topic, key, msg) @mock.patch.object(MsgKafka, "aioread") def test_aioread_once(self, aioread): - future = asyncio.Future(loop=self.loop) + future = asyncio.Future(loop=asyncio.new_event_loop()) future.set_result("mock") aioread.return_value = future - msg_bus = MessageBusClient(self.config, loop=self.loop) + msg_bus = MessageBusClient(self.config) topic = "test_topic" - self.loop.run_until_complete(msg_bus.aioread_once(topic)) - aioread.assert_called_with("test_topic", self.loop) + asyncio.run(msg_bus.aioread_once(topic)) + aioread.assert_called_with("test_topic") diff --git a/requirements-dev.txt b/requirements-dev.txt index b0d6e97..64fe446 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -34,7 +34,7 @@ cachetools==5.3.0 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # google-auth -certifi==2022.12.7 +certifi==2023.5.7 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # kubernetes @@ -48,13 +48,18 @@ charset-normalizer==2.1.1 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # requests -cryptography==39.0.0 +cryptography==40.0.2 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # paramiko dataclasses==0.6 # via -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master -google-auth==2.16.0 +dnspython==2.3.0 + # via + # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master + # -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master + # pymongo +google-auth==2.17.3 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # kubernetes @@ -72,7 +77,7 @@ kafka-python==2.0.2 # via # -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master # aiokafka -kubernetes==25.3.0 +kubernetes==26.1.0 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # juju @@ -81,11 +86,11 @@ macaroonbakery==1.3.1 # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # juju # theblues -motor==1.3.1 +motor==3.1.2 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master -mypy-extensions==0.4.3 +mypy-extensions==1.0.0 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # typing-inspect @@ -93,7 +98,7 @@ oauthlib==3.2.2 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # requests-oauthlib -packaging==23.0 +packaging==23.1 # via # -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master # aiokafka @@ -105,13 +110,13 @@ protobuf==3.20.3 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # macaroonbakery -pyasn1==0.4.8 +pyasn1==0.5.0 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # juju # pyasn1-modules # rsa -pyasn1-modules==0.2.8 +pyasn1-modules==0.3.0 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # google-auth @@ -125,7 +130,7 @@ pymacaroons==0.13.0 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # macaroonbakery -pymongo==3.13.0 +pymongo==4.3.3 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master @@ -145,7 +150,7 @@ python-dateutil==2.8.2 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # kubernetes -pytz==2022.7.1 +pytz==2023.3 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # pyrfc3339 @@ -156,7 +161,7 @@ pyyaml==5.4.1 # juju # jujubundlelib # kubernetes -requests==2.28.2 +requests==2.30.0 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # kubernetes @@ -186,11 +191,11 @@ theblues==0.5.2 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # juju -toposort==1.9 +toposort==1.10 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # juju -typing-extensions==4.4.0 +typing-extensions==4.5.0 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # typing-inspect @@ -198,16 +203,16 @@ typing-inspect==0.8.0 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # juju -urllib3==1.26.14 +urllib3==2.0.2 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # kubernetes # requests -websocket-client==1.5.0 +websocket-client==1.5.1 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # kubernetes -websockets==7.0 +websockets==11.0.3 # via # -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master # juju diff --git a/requirements-test.txt b/requirements-test.txt index b068b79..3cf891d 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. ####################################################################################### -certifi==2022.12.7 +certifi==2023.5.7 # via # -r requirements-test.in # requests @@ -22,19 +22,19 @@ charset-normalizer==2.1.1 # via # -r requirements-test.in # requests -coverage==7.1.0 +coverage==7.2.5 # via -r requirements-test.in idna==3.4 # via requests -mock==5.0.1 +mock==5.0.2 # via -r requirements-test.in -nose2==0.12.0 +nose2==0.13.0 # via -r requirements-test.in -requests==2.28.2 +requests==2.30.0 # via requests-mock requests-mock==1.10.0 # via -r requirements-test.in six==1.16.0 # via requests-mock -urllib3==1.26.14 +urllib3==2.0.2 # via requests diff --git a/requirements.txt b/requirements.txt index e9ff7b3..bf4c4a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,15 +20,11 @@ appdirs==1.4.4 # via openstacksdk async-timeout==4.0.2 # via aiokafka -attrs==22.2.0 +attrs==23.1.0 # via cmd2 autopage==0.5.1 # via cliff -backports-zoneinfo==0.2.1 - # via - # pytz-deprecation-shim - # tzlocal -certifi==2022.12.7 +certifi==2023.5.7 # via # -r requirements.in # requests @@ -40,7 +36,7 @@ charset-normalizer==2.1.1 # via # -r requirements.in # requests -cliff==4.1.0 +cliff==4.3.0 # via # gnocchiclient # osc-lib @@ -49,13 +45,13 @@ cmd2==2.4.3 # via cliff contourpy==1.0.7 # via matplotlib -cryptography==39.0.0 +cryptography==40.0.2 # via # -r requirements.in # openstacksdk cycler==0.11.0 # via matplotlib -dateparser==1.1.6 +dateparser==1.1.8 # via prometheus-api-client debtcollector==2.5.0 # via @@ -70,13 +66,13 @@ decorator==5.1.1 # via # dogpile-cache # openstacksdk -dogpile-cache==1.1.8 +dogpile-cache==1.2.0 # via openstacksdk -fonttools==4.38.0 +fonttools==4.39.4 # via matplotlib futurist==2.4.1 # via gnocchiclient -gnocchiclient==7.0.7 +gnocchiclient==7.0.8 # via -r requirements.in httmock==1.4.0 # via prometheus-api-client @@ -86,7 +82,7 @@ humanfriendly==10.0 # pyvcloud idna==3.4 # via requests -importlib-metadata==6.0.0 +importlib-metadata==6.6.0 # via cliff iso8601==1.1.0 # via @@ -105,7 +101,7 @@ jsonpointer==2.3 # via jsonpatch kafka-python==2.0.2 # via aiokafka -keystoneauth1==5.1.1 +keystoneauth1==5.1.2 # via # gnocchiclient # openstacksdk @@ -122,14 +118,10 @@ lxml==4.9.2 # -r requirements.in # pyvcloud # unittest-xml-reporting -matplotlib==3.6.3 +matplotlib==3.7.1 # via prometheus-api-client -monotonic==1.6 - # via gnocchiclient -msgpack==1.0.4 +msgpack==1.0.5 # via oslo-serialization -munch==2.5.0 - # via openstacksdk netaddr==0.8.0 # via # oslo-config @@ -139,13 +131,13 @@ netifaces==0.11.0 # via # openstacksdk # oslo-utils -numpy==1.24.1 +numpy==1.24.3 # via # contourpy # matplotlib # pandas # prometheus-api-client -openstacksdk==0.103.0 +openstacksdk==1.1.0 # via # os-client-config # osc-lib @@ -155,15 +147,15 @@ os-service-types==1.7.0 # via # keystoneauth1 # openstacksdk -osc-lib==2.6.2 +osc-lib==2.8.0 # via python-neutronclient -oslo-config==9.1.0 +oslo-config==9.1.1 # via # oslo-log # python-keystoneclient -oslo-context==5.0.0 +oslo-context==5.1.1 # via oslo-log -oslo-i18n==5.1.0 +oslo-i18n==6.0.0 # via # osc-lib # oslo-config @@ -174,9 +166,9 @@ oslo-i18n==5.1.0 # python-keystoneclient # python-neutronclient # python-novaclient -oslo-log==5.0.2 +oslo-log==5.2.0 # via python-neutronclient -oslo-serialization==5.0.0 +oslo-serialization==5.1.1 # via # oslo-log # python-ceilometerclient @@ -193,18 +185,17 @@ oslo-utils==6.1.0 # python-keystoneclient # python-neutronclient # python-novaclient -packaging==23.0 +packaging==23.1 # via # aiokafka # matplotlib # oslo-utils # python-keystoneclient # pyvcloud -pandas==1.5.3 +pandas==2.0.1 # via prometheus-api-client pbr==5.11.1 # via - # gnocchiclient # keystoneauth1 # openstacksdk # os-service-types @@ -219,7 +210,7 @@ pbr==5.11.1 # python-neutronclient # python-novaclient # stevedore -pillow==9.4.0 +pillow==9.5.0 # via matplotlib prettytable==0.7.2 # via @@ -227,13 +218,13 @@ prettytable==0.7.2 # python-ceilometerclient # python-cinderclient # python-novaclient -prometheus-api-client==0.5.2 +prometheus-api-client==0.5.3 # via -r requirements.in prometheus-client==0.16.0 # via -r requirements.in pycparser==2.21 # via cffi -pygments==2.14.0 +pygments==2.15.1 # via pyvcloud pyinotify==0.9.6 # via oslo-log @@ -255,15 +246,15 @@ python-dateutil==2.8.2 # oslo-log # pandas # pyvcloud -python-keystoneclient==5.0.1 +python-keystoneclient==5.1.0 # via # -r requirements.in # python-neutronclient -python-neutronclient==8.2.1 +python-neutronclient==9.0.0 # via -r requirements.in -python-novaclient==18.2.0 +python-novaclient==18.3.0 # via -r requirements.in -pytz==2022.7.1 +pytz==2023.3 # via # dateparser # oslo-serialization @@ -280,9 +271,9 @@ pyyaml==5.4.1 # openstacksdk # oslo-config # pyvcloud -regex==2022.10.31 +regex==2023.5.5 # via dateparser -requests==2.28.2 +requests==2.30.0 # via # -r requirements.in # httmock @@ -298,7 +289,7 @@ requestsexceptions==1.4.0 # via openstacksdk rfc3986==2.0.0 # via oslo-config -simplejson==3.18.1 +simplejson==3.19.1 # via # osc-lib # python-cinderclient @@ -308,11 +299,10 @@ six==1.16.0 # -r requirements.in # gnocchiclient # keystoneauth1 - # munch # python-ceilometerclient # python-dateutil # python-keystoneclient -stevedore==4.1.1 +stevedore==5.0.0 # via # cliff # dogpile-cache @@ -323,21 +313,23 @@ stevedore==4.1.1 # python-cinderclient # python-keystoneclient # python-novaclient -tzdata==2022.7 - # via pytz-deprecation-shim -tzlocal==4.2 +tzdata==2023.3 + # via + # pandas + # pytz-deprecation-shim +tzlocal==4.3 # via dateparser ujson==5.7.0 # via gnocchiclient unittest-xml-reporting==3.2.0 # via pyvcloud -urllib3==1.26.14 +urllib3==2.0.2 # via requests vcd-api-schemas-type==10.3.0.dev72 # via pyvcloud wcwidth==0.2.6 # via cmd2 -wrapt==1.14.1 +wrapt==1.15.0 # via debtcollector -zipp==3.12.0 +zipp==3.15.0 # via importlib-metadata diff --git a/tox.ini b/tox.ini index 6600772..21c559d 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,7 @@ toxworkdir = /tmp/.tox [testenv] usedevelop = True -basepython = python3.8 +basepython = python3.10 setenv = VIRTUAL_ENV={envdir} PYTHONDONTWRITEBYTECODE = 1 deps = -r{toxinidir}/requirements.txt