From 115ef7d55ca009344a1b2ffc75d2a73fd100e1d4 Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Tue, 8 Jul 2025 10:17:05 -0400 Subject: [PATCH 01/10] Switch to using pathlib --- newrelic/__init__.py | 8 ++-- newrelic/admin/debug_console.py | 3 +- newrelic/admin/generate_config.py | 15 +++---- newrelic/admin/license_key.py | 5 ++- newrelic/admin/local_config.py | 5 ++- newrelic/admin/network_config.py | 5 ++- newrelic/admin/run_program.py | 24 ++++++----- newrelic/admin/run_python.py | 24 ++++++----- newrelic/admin/server_config.py | 5 ++- newrelic/admin/validate_config.py | 5 ++- newrelic/api/profile_trace.py | 5 ++- newrelic/bootstrap/sitecustomize.py | 7 +-- newrelic/common/agent_http.py | 3 +- newrelic/common/certs.py | 4 +- newrelic/common/system_info.py | 19 ++++---- newrelic/common/utilization.py | 5 ++- newrelic/config.py | 3 +- newrelic/console.py | 13 +++--- newrelic/core/agent_control_health.py | 4 +- newrelic/core/agent_protocol.py | 5 ++- newrelic/core/profile_sessions.py | 3 +- pyproject.toml | 2 +- .../test_aiohttp_app_factory.py | 5 ++- tests/adapter_gunicorn/test_asgi_app.py | 6 +-- tests/adapter_gunicorn/test_gaiohttp.py | 6 +-- .../test_agent_control_health_check.py | 7 ++- tests/agent_unittests/test_agent_protocol.py | 3 +- .../agent_unittests/test_check_environment.py | 15 +++---- tests/agent_unittests/test_harvest_loop.py | 5 ++- .../component_djangorestframework/settings.py | 4 +- tests/component_tastypie/settings.py | 4 +- tests/cross_agent/test_agent_attributes.py | 9 ++-- .../cross_agent/test_aws_utilization_data.py | 7 ++- .../test_azure_utilization_data.py | 7 ++- .../test_boot_id_utilization_data.py | 7 ++- tests/cross_agent/test_cat_map.py | 8 ++-- tests/cross_agent/test_collector_hostname.py | 6 +-- tests/cross_agent/test_datastore_instance.py | 7 ++- tests/cross_agent/test_distributed_tracing.py | 8 ++-- tests/cross_agent/test_docker_container_id.py | 13 +++--- .../test_docker_container_id_v2.py | 13 +++--- .../cross_agent/test_gcp_utilization_data.py | 7 ++- tests/cross_agent/test_labels_and_rollups.py | 8 ++-- tests/cross_agent/test_lambda_event_source.py | 13 +++--- .../cross_agent/test_pcf_utilization_data.py | 7 ++- tests/cross_agent/test_rules.py | 7 ++- tests/cross_agent/test_sql_obfuscation.py | 8 ++-- tests/cross_agent/test_system_info.py | 10 ++--- .../test_transaction_segment_terms.py | 7 ++- tests/cross_agent/test_utilization_configs.py | 6 +-- tests/cross_agent/test_w3c_trace_context.py | 9 ++-- tests/external_botocore/conftest.py | 5 ++- tests/external_botocore/test_s3transfer.py | 4 +- tests/external_feedparser/conftest.py | 4 +- tests/external_feedparser/test_feedparser.py | 4 +- tests/external_httplib/test_urllib.py | 4 +- tests/external_httplib/test_urllib2.py | 3 +- .../framework_ariadne/_target_schema_async.py | 4 +- .../framework_ariadne/_target_schema_sync.py | 4 +- tests/framework_django/settings.py | 6 +-- tests/mlmodel_gemini/conftest.py | 5 ++- tests/mlmodel_langchain/conftest.py | 5 ++- .../new_vectorstore_adder.py | 12 +++--- tests/mlmodel_langchain/test_vectorstore.py | 43 ++++++++----------- tests/mlmodel_openai/conftest.py | 5 ++- tests/testing_support/certs/__init__.py | 4 +- tests/testing_support/fixtures.py | 11 ++--- 67 files changed, 255 insertions(+), 262 deletions(-) diff --git a/newrelic/__init__.py b/newrelic/__init__.py index e796623b0e..c27a5e98e7 100644 --- a/newrelic/__init__.py +++ b/newrelic/__init__.py @@ -12,14 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os.path +from pathlib import Path -THIS_DIR = os.path.dirname(__file__) +VERSION_FILE = Path(__file__).parent / "version.txt" try: - with open(os.path.join(THIS_DIR, "version.txt")) as f: + with VERSION_FILE.open() as f: version = f.read() -except: +except Exception: version = "0.0.0" version_info = list(map(int, version.split("."))) diff --git a/newrelic/admin/debug_console.py b/newrelic/admin/debug_console.py index 4b123960c4..09d118e98f 100644 --- a/newrelic/admin/debug_console.py +++ b/newrelic/admin/debug_console.py @@ -25,6 +25,7 @@ ) def debug_console(args): import sys + from pathlib import Path if len(args) == 0: usage("debug-console") @@ -36,7 +37,7 @@ def debug_console(args): log_object = None if len(args) >= 2: - log_object = open(args[1], "w") + log_object = Path(args[1]).open("w") shell = ClientShell(config_file, log=log_object) shell.cmdloop() diff --git a/newrelic/admin/generate_config.py b/newrelic/admin/generate_config.py index 8b065b792c..c6c66c9b6a 100644 --- a/newrelic/admin/generate_config.py +++ b/newrelic/admin/generate_config.py @@ -19,28 +19,25 @@ "generate-config", "license_key [output_file]", """Generates a sample agent configuration file for .""" ) def generate_config(args): - import os import sys + from pathlib import Path if len(args) == 0: usage("generate-config") sys.exit(1) - from newrelic import __file__ as package_root + import newrelic - package_root = os.path.dirname(package_root) + config_file = Path(newrelic.__file__).parent / "newrelic.ini" - config_file = os.path.join(package_root, "newrelic.ini") - - with open(config_file) as f: + with config_file.open() as f: content = f.read() if len(args) >= 1: content = content.replace("*** REPLACE ME ***", args[0]) if len(args) >= 2 and args[1] != "-": - output_file = open(args[1], "w") - output_file.write(content) - output_file.close() + with Path(args[1]).open("w") as output_file: + output_file.write(content) else: print(content) diff --git a/newrelic/admin/license_key.py b/newrelic/admin/license_key.py index 4effdbd820..a927557540 100644 --- a/newrelic/admin/license_key.py +++ b/newrelic/admin/license_key.py @@ -26,6 +26,7 @@ def license_key(args): import logging import os import sys + from pathlib import Path if len(args) == 0: usage("license-key") @@ -37,12 +38,12 @@ def license_key(args): if len(args) >= 2: log_file = args[1] else: - log_file = "/tmp/python-agent-test.log" + log_file = Path("/tmp/python-agent-test.log") log_level = logging.DEBUG try: - os.unlink(log_file) + log_file.unlink() except Exception: pass diff --git a/newrelic/admin/local_config.py b/newrelic/admin/local_config.py index 6c622c40ce..7b9ff15404 100644 --- a/newrelic/admin/local_config.py +++ b/newrelic/admin/local_config.py @@ -25,6 +25,7 @@ def local_config(args): import logging import os import sys + from pathlib import Path if len(args) == 0: usage("local-config") @@ -36,12 +37,12 @@ def local_config(args): if len(args) >= 2: log_file = args[1] else: - log_file = "/tmp/python-agent-test.log" + log_file = Path("/tmp/python-agent-test.log") log_level = logging.DEBUG try: - os.unlink(log_file) + log_file.unlink() except Exception: pass diff --git a/newrelic/admin/network_config.py b/newrelic/admin/network_config.py index 8bb15b818e..5e7fd1c9a6 100644 --- a/newrelic/admin/network_config.py +++ b/newrelic/admin/network_config.py @@ -25,6 +25,7 @@ def network_config(args): import logging import os import sys + from pathlib import Path if len(args) == 0: usage("network-config") @@ -36,12 +37,12 @@ def network_config(args): if len(args) >= 2: log_file = args[1] else: - log_file = "/tmp/python-agent-test.log" + log_file = Path("/tmp/python-agent-test.log") log_level = logging.DEBUG try: - os.unlink(log_file) + log_file.unlink() except Exception: pass diff --git a/newrelic/admin/run_program.py b/newrelic/admin/run_program.py index fb61717312..cf64f064f7 100644 --- a/newrelic/admin/run_program.py +++ b/newrelic/admin/run_program.py @@ -31,6 +31,7 @@ def run_program(args): import os import sys import time + from pathlib import Path if len(args) == 0: usage("run-program") @@ -46,7 +47,7 @@ def log_message(text, *args): log_message("New Relic Admin Script (%s)", __file__) - log_message("working_directory = %r", os.getcwd()) + log_message("working_directory = %r", str(Path.cwd())) log_message("current_command = %r", sys.argv) log_message("sys.prefix = %r", os.path.normpath(sys.prefix)) @@ -67,10 +68,13 @@ def log_message(text, *args): continue log_message("%s = %r", name, os.environ.get(name)) - from newrelic import __file__ as root_directory + import newrelic - root_directory = os.path.dirname(root_directory) - boot_directory = os.path.join(root_directory, "bootstrap") + root_directory = Path(newrelic.__file__).parent + boot_directory = root_directory / "bootstrap" + + root_directory = str(root_directory) + boot_directory = str(boot_directory) log_message("root_directory = %r", root_directory) log_message("boot_directory = %r", boot_directory) @@ -94,17 +98,17 @@ def log_message(text, *args): # be found in current working directory even though '.' # not in PATH. - program_exe_path = args[0] + program_exe_path = Path(args[0]) - if not os.path.dirname(program_exe_path): + if not program_exe_path.parent: program_search_path = os.environ.get("PATH", "").split(os.path.pathsep) for path in program_search_path: - path = os.path.join(path, program_exe_path) - if os.path.exists(path) and os.access(path, os.X_OK): + path = Path(path) / program_exe_path + if path.exists() and os.access(path, os.X_OK): program_exe_path = path break - log_message("program_exe_path = %r", program_exe_path) - log_message("execl_arguments = %r", [program_exe_path] + args) + log_message("program_exe_path = %r", str(program_exe_path)) + log_message("execl_arguments = %r", [program_exe_path, *args]) os.execl(program_exe_path, *args) # noqa: S606 diff --git a/newrelic/admin/run_python.py b/newrelic/admin/run_python.py index 956afdcc16..f9c84f233e 100644 --- a/newrelic/admin/run_python.py +++ b/newrelic/admin/run_python.py @@ -31,6 +31,7 @@ def run_python(args): import os import sys import time + from pathlib import Path startup_debug = os.environ.get("NEW_RELIC_STARTUP_DEBUG", "off").lower() in ("on", "true", "1") @@ -42,7 +43,7 @@ def log_message(text, *args): log_message("New Relic Admin Script (%s)", __file__) - log_message("working_directory = %r", os.getcwd()) + log_message("working_directory = %r", str(Path.cwd())) log_message("current_command = %r", sys.argv) log_message("sys.prefix = %r", os.path.normpath(sys.prefix)) @@ -61,10 +62,13 @@ def log_message(text, *args): if name.startswith("NEW_RELIC_") or name.startswith("PYTHON"): log_message("%s = %r", name, os.environ.get(name)) - from newrelic import __file__ as root_directory + import newrelic - root_directory = os.path.dirname(root_directory) - boot_directory = os.path.join(root_directory, "bootstrap") + root_directory = Path(newrelic.__file__).parent + boot_directory = root_directory / "bootstrap" + + root_directory = str(root_directory) + boot_directory = str(boot_directory) log_message("root_directory = %r", root_directory) log_message("boot_directory = %r", boot_directory) @@ -91,17 +95,17 @@ def log_message(text, *args): # this script in preference to that used to execute this # script. - bin_directory = os.path.dirname(sys.argv[0]) + bin_directory = Path(sys.argv[0]).parent if bin_directory: - python_exe = os.path.basename(sys.executable) - python_exe_path = os.path.join(bin_directory, python_exe) - if not os.path.exists(python_exe_path) or not os.access(python_exe_path, os.X_OK): + python_exe = Path(sys.executable).name + python_exe_path = bin_directory / python_exe + if not python_exe_path.exists() or not os.access(python_exe_path, os.X_OK): python_exe_path = sys.executable else: python_exe_path = sys.executable - log_message("python_exe_path = %r", python_exe_path) - log_message("execl_arguments = %r", [python_exe_path, python_exe_path] + args) + log_message("python_exe_path = %r", str(python_exe_path)) + log_message("execl_arguments = %r", [python_exe_path, python_exe_path, *args]) os.execl(python_exe_path, python_exe_path, *args) # noqa: S606 diff --git a/newrelic/admin/server_config.py b/newrelic/admin/server_config.py index 978373c49d..cd05870dcb 100644 --- a/newrelic/admin/server_config.py +++ b/newrelic/admin/server_config.py @@ -28,6 +28,7 @@ def server_config(args): import os import sys import time + from pathlib import Path if len(args) == 0: usage("server-config") @@ -39,12 +40,12 @@ def server_config(args): if len(args) >= 2: log_file = args[1] else: - log_file = "/tmp/python-agent-test.log" + log_file = Path("/tmp/python-agent-test.log") log_level = logging.DEBUG try: - os.unlink(log_file) + log_file.unlink() except Exception: pass diff --git a/newrelic/admin/validate_config.py b/newrelic/admin/validate_config.py index 1b8009ceff..af0f7bf722 100644 --- a/newrelic/admin/validate_config.py +++ b/newrelic/admin/validate_config.py @@ -141,6 +141,7 @@ def validate_config(args): import os import sys import time + from pathlib import Path if len(args) == 0: usage("validate-config") @@ -154,12 +155,12 @@ def validate_config(args): if len(args) >= 2: log_file = args[1] else: - log_file = "/tmp/python-agent-test.log" + log_file = Path("/tmp/python-agent-test.log") log_level = logging.DEBUG try: - os.unlink(log_file) + log_file.unlink() except Exception: pass diff --git a/newrelic/api/profile_trace.py b/newrelic/api/profile_trace.py index 782d6918c8..f761486f95 100644 --- a/newrelic/api/profile_trace.py +++ b/newrelic/api/profile_trace.py @@ -15,14 +15,15 @@ import functools import os import sys +from pathlib import Path -from newrelic import __file__ as AGENT_PACKAGE_FILE +import newrelic from newrelic.api.function_trace import FunctionTrace from newrelic.api.time_trace import current_trace from newrelic.common.object_names import callable_name from newrelic.common.object_wrapper import FunctionWrapper, wrap_object -AGENT_PACKAGE_DIRECTORY = f"{os.path.dirname(AGENT_PACKAGE_FILE)}/" +AGENT_PACKAGE_DIRECTORY = str(Path(newrelic.__file__).parent) + os.sep class ProfileTrace: diff --git a/newrelic/bootstrap/sitecustomize.py b/newrelic/bootstrap/sitecustomize.py index 894d58539f..3ec60afd7f 100644 --- a/newrelic/bootstrap/sitecustomize.py +++ b/newrelic/bootstrap/sitecustomize.py @@ -16,6 +16,7 @@ import sys import time from importlib.machinery import PathFinder +from pathlib import Path # Define some debug logging routines to help sort out things when this # all doesn't work as expected. @@ -42,7 +43,7 @@ def del_sys_path_entry(path): log_message("New Relic Bootstrap (%s)", __file__) -log_message("working_directory = %r", os.getcwd()) +log_message("working_directory = %r", str(Path.cwd())) log_message("sys.prefix = %r", os.path.normpath(sys.prefix)) @@ -74,7 +75,7 @@ def del_sys_path_entry(path): # imp module to find the module, excluding the bootstrap directory from # the search, and then load what was found. -boot_directory = os.path.dirname(__file__) +boot_directory = str(Path(__file__).parent) log_message("boot_directory = %r", boot_directory) del_sys_path_entry(boot_directory) @@ -160,7 +161,7 @@ def del_sys_path_entry(path): # 'newrelic' module to reduce chance that will cause any issues. # If it is a buildout created script, it will replace the whole # sys.path again later anyway. - root_directory = os.path.dirname(os.path.dirname(boot_directory)) + root_directory = str(Path(boot_directory).parent.parent) log_message("root_directory = %r", root_directory) new_relic_path = root_directory diff --git a/newrelic/common/agent_http.py b/newrelic/common/agent_http.py index 5b8e63da51..48a3d2e76f 100644 --- a/newrelic/common/agent_http.py +++ b/newrelic/common/agent_http.py @@ -16,6 +16,7 @@ import sys import time import zlib +from pathlib import Path from pprint import pprint from newrelic import version @@ -264,7 +265,7 @@ def __init__( internal_metric("Supportability/Python/Certificate/BundleRequired", 1) if ca_bundle_path: - if os.path.isdir(ca_bundle_path): + if Path(ca_bundle_path).is_dir(): connection_kwargs["ca_cert_dir"] = ca_bundle_path else: connection_kwargs["ca_certs"] = ca_bundle_path diff --git a/newrelic/common/certs.py b/newrelic/common/certs.py index 2a93a698f2..53e1284a2f 100644 --- a/newrelic/common/certs.py +++ b/newrelic/common/certs.py @@ -14,8 +14,8 @@ """This module returns the CA certificate bundle included with the agent.""" -import os +from pathlib import Path def where(): - return os.path.join(os.path.dirname(__file__), "cacert.pem") + return Path(__file__).parent / "cacert.pem" diff --git a/newrelic/common/system_info.py b/newrelic/common/system_info.py index 1b2c720fe5..1154b9a041 100644 --- a/newrelic/common/system_info.py +++ b/newrelic/common/system_info.py @@ -24,6 +24,7 @@ import subprocess import sys import threading +from pathlib import Path from subprocess import check_output as _execute_program from newrelic.common.utilization import CommonUtilization @@ -74,7 +75,7 @@ def logical_processor_count(): # looking at the devices corresponding to the available CPUs. try: - pseudoDevices = os.listdir("/devices/pseudo/") + pseudoDevices = Path("/devices/pseudo/").iterdir() expr = re.compile("^cpuid@[0-9]+$") res = 0 @@ -106,13 +107,13 @@ def _linux_physical_processor_count(filename=None): # provide the total number of cores for that physical processor. # The 'cpu cores' field is duplicated, so only remember the last - filename = filename or "/proc/cpuinfo" + filename = Path(filename or "/proc/cpuinfo") processors = 0 physical_processors = {} try: - with open(filename) as fp: + with filename.open() as fp: processor_id = None cores = None @@ -205,13 +206,13 @@ def _linux_total_physical_memory(filename=None): # units is given in the file, it is always in kilobytes so we do not # need to accommodate any other unit types beside 'kB'. - filename = filename or "/proc/meminfo" + filename = Path(filename or "/proc/meminfo") try: parser = re.compile(r"^(?P\S*):\s*(?P\d*)\s*kB") - with open(filename) as fp: - for line in fp.readlines(): # noqa: FURB129 # Read all lines at once + with filename.open() as fp: + for line in fp.readlines(): # Read all lines at once match = parser.match(line) if not match: continue @@ -271,10 +272,10 @@ def _linux_physical_memory_used(filename=None): # data data + stack # dt dirty pages (unused in Linux 2.6) - filename = filename or f"/proc/{os.getpid()}/statm" + filename = Path(filename or f"/proc/{os.getpid()}/statm") try: - with open(filename) as fp: + with filename.open() as fp: rss_pages = float(fp.read().split()[1]) memory_bytes = rss_pages * resource.getpagesize() return memory_bytes / (1024 * 1024) @@ -406,7 +407,7 @@ def fetch(cls): return try: - with open(cls.METADATA_URL, "rb") as f: + with Path(cls.METADATA_URL).open("rb") as f: return f.readline().decode("ascii") except: # There are all sorts of exceptions that can occur here diff --git a/newrelic/common/utilization.py b/newrelic/common/utilization.py index c3f3b3435e..99e8e29faa 100644 --- a/newrelic/common/utilization.py +++ b/newrelic/common/utilization.py @@ -18,6 +18,7 @@ import re import socket import string +from pathlib import Path from newrelic.common.agent_http import InsecureHttpClient from newrelic.common.encoding_utils import json_decode @@ -309,7 +310,7 @@ class DockerUtilization(CommonUtilization): def fetch(cls): # Try to read from cgroups try: - with open(cls.METADATA_FILE_CGROUPS_V1, "rb") as f: + with Path(cls.METADATA_FILE_CGROUPS_V1).open("rb") as f: for line in f: stripped = line.decode("utf-8").strip() cgroup = stripped.split(":") @@ -328,7 +329,7 @@ def fetch(cls): # Fallback to reading from mountinfo try: - with open(cls.METADATA_FILE_CGROUPS_V2, "rb") as f: + with Path(cls.METADATA_FILE_CGROUPS_V2).open("rb") as f: for line in f: stripped = line.decode("utf-8").strip() match = cls.METADATA_RE_CGROUPS_V2.match(stripped) diff --git a/newrelic/config.py b/newrelic/config.py index b5b47b4f3a..0b2ad73567 100644 --- a/newrelic/config.py +++ b/newrelic/config.py @@ -20,6 +20,7 @@ import threading import time import traceback +from pathlib import Path import newrelic.api.application import newrelic.api.background_task @@ -937,7 +938,7 @@ def _load_configuration(config_file=None, environment=None, ignore_errors=True, raise newrelic.api.exceptions.ConfigurationError( "TOML configuration file can only be used if tomllib is available (Python 3.11+)." ) from exc - with open(config_file, "rb") as f: + with Path(config_file).open("rb") as f: content = tomllib.load(f) newrelic_section = content.get("tool", {}).get("newrelic") if not newrelic_section: diff --git a/newrelic/console.py b/newrelic/console.py index 1bdd6c17b0..2ed64dead9 100644 --- a/newrelic/console.py +++ b/newrelic/console.py @@ -30,6 +30,7 @@ import traceback from collections import OrderedDict from inspect import signature +from pathlib import Path from newrelic.common.object_wrapper import ObjectProxy from newrelic.core.agent import agent_instance @@ -423,7 +424,7 @@ def __init__(self, listener_socket): self.__listener_socket = listener_socket self.__console_initialized = False - if not os.path.isabs(self.__listener_socket): + if not Path(self.__listener_socket).is_absolute(): host, port = self.__listener_socket.split(":") port = int(port) self.__listener_socket = (host, port) @@ -435,7 +436,7 @@ def __init__(self, listener_socket): def __socket_cleanup(self, path): try: - os.unlink(path) + Path(path).unlink() except Exception: pass @@ -446,7 +447,7 @@ def __thread_run(self): listener.bind(self.__listener_socket) else: try: - os.unlink(self.__listener_socket) + Path(self.__listener_socket).unlink() except Exception: pass @@ -454,7 +455,7 @@ def __thread_run(self): listener.bind(self.__listener_socket) atexit.register(self.__socket_cleanup, self.__listener_socket) - os.chmod(self.__listener_socket, 0o600) + Path(self.__listener_socket).chmod(0o600) listener.listen(5) @@ -509,8 +510,8 @@ def __init__(self, config_file, stdin=None, stdout=None, log=None): listener_socket = self.__config_object.get("newrelic", "console.listener_socket") % {"pid": "*"} - if os.path.isabs(listener_socket): - self.__servers = [(socket.AF_UNIX, path) for path in sorted(glob.glob(listener_socket))] + if Path(listener_socket).is_absolute(): + self.__servers = [(socket.AF_UNIX, path) for path in sorted(glob.glob(listener_socket))] # noqa: PTH207 else: host, port = listener_socket.split(":") port = int(port) diff --git a/newrelic/core/agent_control_health.py b/newrelic/core/agent_control_health.py index bdaf6225a7..33e49a8a16 100644 --- a/newrelic/core/agent_control_health.py +++ b/newrelic/core/agent_control_health.py @@ -188,10 +188,10 @@ def write_to_health_file(self): file_path = urlparse(self.health_delivery_location).path file_id = self.get_file_id() file_name = f"health-{file_id}.yml" - full_path = os.path.join(file_path, file_name) + full_path = Path(file_path) / file_name is_healthy = self.is_healthy - with open(full_path, "w") as f: + with full_path.open("w") as f: f.write(f"healthy: {is_healthy}\n") f.write(f"status: {self.status_message}\n") f.write(f"start_time_unix_nano: {self.start_time_unix_nano}\n") diff --git a/newrelic/core/agent_protocol.py b/newrelic/core/agent_protocol.py index f8b4642c30..38a59e928d 100644 --- a/newrelic/core/agent_protocol.py +++ b/newrelic/core/agent_protocol.py @@ -14,6 +14,7 @@ import logging import os +from pathlib import Path from newrelic import version from newrelic.common import system_info @@ -154,7 +155,7 @@ class AgentProtocol: def __init__(self, settings, host=None, client_cls=ApplicationModeClient): if settings.audit_log_file: - audit_log_fp = open(settings.audit_log_file, "a") + audit_log_fp = Path(settings.audit_log_file).open("a") else: audit_log_fp = None @@ -531,7 +532,7 @@ def connect(cls, app_name, linked_applications, environment, settings, client_cl class OtlpProtocol(AgentProtocol): def __init__(self, settings, host=None, client_cls=ApplicationModeClient): if settings.audit_log_file: - audit_log_fp = open(settings.audit_log_file, "a") + audit_log_fp = Path(settings.audit_log_file).open("a") else: audit_log_fp = None diff --git a/newrelic/core/profile_sessions.py b/newrelic/core/profile_sessions.py index dd16cb4528..22d70ceb40 100644 --- a/newrelic/core/profile_sessions.py +++ b/newrelic/core/profile_sessions.py @@ -19,6 +19,7 @@ import time import zlib from collections import defaultdict, deque +from pathlib import Path from sys import intern import newrelic @@ -28,7 +29,7 @@ _logger = logging.getLogger(__name__) -AGENT_PACKAGE_DIRECTORY = os.path.dirname(newrelic.__file__) + os.sep +AGENT_PACKAGE_DIRECTORY = str(Path(newrelic.__file__).parent) + os.sep class SessionState: diff --git a/pyproject.toml b/pyproject.toml index 8a5649aab9..ae7fd88c13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ select = [ "TID", # flake8-tidy-imports "INT", # flake8-gettext # "ARG", # flake8-unused-arguments - # "PTH", # flake8-use-pathlib + "PTH", # flake8-use-pathlib "PGH", # pygrep-hooks "PL", # Pylint # "TRY", # tryceratops diff --git a/tests/adapter_gunicorn/test_aiohttp_app_factory.py b/tests/adapter_gunicorn/test_aiohttp_app_factory.py index 5e2a34f143..a903f9185b 100644 --- a/tests/adapter_gunicorn/test_aiohttp_app_factory.py +++ b/tests/adapter_gunicorn/test_aiohttp_app_factory.py @@ -15,6 +15,7 @@ import os import socket import time +from pathlib import Path from urllib.request import urlopen import pytest @@ -29,8 +30,8 @@ @pytest.mark.skipif(version_info < (3, 1), reason="aiohttp app factories were implement in 3.1") @pytest.mark.parametrize("nr_enabled", (True, False)) def test_aiohttp_app_factory(nr_enabled): - nr_admin = os.path.join(os.environ["TOX_ENV_DIR"], "bin", "newrelic-admin") - gunicorn = os.path.join(os.environ["TOX_ENV_DIR"], "bin", "gunicorn") + nr_admin = Path(os.environ["TOX_ENV_DIR"]) / "bin" / "newrelic-admin" + gunicorn = Path(os.environ["TOX_ENV_DIR"]) / "bin" / "gunicorn" # Restart the server if it dies during testing for _ in range(5): diff --git a/tests/adapter_gunicorn/test_asgi_app.py b/tests/adapter_gunicorn/test_asgi_app.py index 3004e83c32..bfca1b593b 100644 --- a/tests/adapter_gunicorn/test_asgi_app.py +++ b/tests/adapter_gunicorn/test_asgi_app.py @@ -13,9 +13,9 @@ # limitations under the License. import os -import random import socket import time +from pathlib import Path import pytest from testing_support.fixtures import TerminatingPopen @@ -29,8 +29,8 @@ @pytest.mark.parametrize("nr_enabled", (True, False)) def test_asgi_app(nr_enabled): - nr_admin = os.path.join(os.environ["TOX_ENV_DIR"], "bin", "newrelic-admin") - gunicorn = os.path.join(os.environ["TOX_ENV_DIR"], "bin", "gunicorn") + nr_admin = Path(os.environ["TOX_ENV_DIR"]) / "bin" / "newrelic-admin" + gunicorn = Path(os.environ["TOX_ENV_DIR"]) / "bin" / "gunicorn" PORT = get_open_port() cmd = [gunicorn, "-b", f"127.0.0.1:{PORT}", "--worker-class", "worker.AsgiWorker", "asgi_app:Application"] diff --git a/tests/adapter_gunicorn/test_gaiohttp.py b/tests/adapter_gunicorn/test_gaiohttp.py index eaaac78089..93725a0ae0 100644 --- a/tests/adapter_gunicorn/test_gaiohttp.py +++ b/tests/adapter_gunicorn/test_gaiohttp.py @@ -13,9 +13,9 @@ # limitations under the License. import os -import random import socket import time +from pathlib import Path import pytest from testing_support.fixtures import TerminatingPopen @@ -30,8 +30,8 @@ @pytest.mark.parametrize("nr_enabled", [True, False]) def test_gunicorn_gaiohttp_worker(nr_enabled): - nr_admin = os.path.join(os.environ["TOX_ENV_DIR"], "bin", "newrelic-admin") - gunicorn = os.path.join(os.environ["TOX_ENV_DIR"], "bin", "gunicorn") + nr_admin = Path(os.environ["TOX_ENV_DIR"]) / "bin" / "newrelic-admin" + gunicorn = Path(os.environ["TOX_ENV_DIR"]) / "bin" / "gunicorn" # Restart the server if it dies during testing for _ in range(5): diff --git a/tests/agent_features/test_agent_control_health_check.py b/tests/agent_features/test_agent_control_health_check.py index 51efde22fc..a8ceb893e4 100644 --- a/tests/agent_features/test_agent_control_health_check.py +++ b/tests/agent_features/test_agent_control_health_check.py @@ -11,10 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import os import re import threading import time +from pathlib import Path import pytest from testing_support.fixtures import initialize_agent @@ -34,9 +34,8 @@ def get_health_file_contents(tmp_path): # Grab the file we just wrote to and read its contents - health_files = os.listdir(tmp_path) - path_to_written_file = f"{tmp_path}/{health_files[0]}" - with open(path_to_written_file) as f: + health_file = list(Path(tmp_path).iterdir())[0] + with health_file.open() as f: contents = f.readlines() return contents diff --git a/tests/agent_unittests/test_agent_protocol.py b/tests/agent_unittests/test_agent_protocol.py index 56761bf543..4bcf7dc579 100644 --- a/tests/agent_unittests/test_agent_protocol.py +++ b/tests/agent_unittests/test_agent_protocol.py @@ -16,6 +16,7 @@ import os import ssl import tempfile +from pathlib import Path import pytest from testing_support.http_client_recorder import HttpClientRecorder @@ -521,7 +522,7 @@ def test_audit_logging(): protocol = AgentProtocol(settings, client_cls=HttpClientRecorder) protocol.send("preconnect") - with open(f.name) as f: + with Path(f.name).open() as f: audit_log_contents = f.read() assert audit_log_contents.startswith("*\n") diff --git a/tests/agent_unittests/test_check_environment.py b/tests/agent_unittests/test_check_environment.py index c9dd480c55..887a132779 100644 --- a/tests/agent_unittests/test_check_environment.py +++ b/tests/agent_unittests/test_check_environment.py @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import shutil import sys import tempfile +from pathlib import Path import pytest @@ -24,15 +24,14 @@ @pytest.mark.parametrize("content", [{}, {"opt": [1, 2, 3]}]) def test_check_environment_failing(content): - temp_dir = tempfile.mkdtemp() + temp_dir = Path(tempfile.mkdtemp()) try: - uwsgi_dir = os.path.join(temp_dir, "uwsgi") - init_file = os.path.join(uwsgi_dir, "__init__.py") - os.makedirs(uwsgi_dir) - with open(init_file, "w") as f: - for key, value in content.items(): - f.write(f"{key} = {value}") + uwsgi_dir = temp_dir / "uwsgi" + init_file = uwsgi_dir / "__init__.py" + uwsgi_dir.mkdir(parents=True) + with init_file.open("w") as f: + f.writelines(f"{key} = {value}" for key, value in content.items()) sys.path.insert(0, temp_dir) import uwsgi diff --git a/tests/agent_unittests/test_harvest_loop.py b/tests/agent_unittests/test_harvest_loop.py index e4f21c3d4b..0439ba1650 100644 --- a/tests/agent_unittests/test_harvest_loop.py +++ b/tests/agent_unittests/test_harvest_loop.py @@ -15,6 +15,7 @@ import random import tempfile import time +from pathlib import Path import pytest from testing_support.fixtures import failing_endpoint, function_not_called, override_generic_settings @@ -295,7 +296,7 @@ def test_application_harvest(): assert endpoints_called[-2] == "metric_data" # verify audit log is not empty - with open(settings.audit_log_file, "rb") as f: + with Path(settings.audit_log_file).open("rb") as f: assert f.read() @@ -314,7 +315,7 @@ def test_serverless_application_harvest(): app.harvest() # verify audit log is not empty - with open(settings.audit_log_file, "rb") as f: + with Path(settings.audit_log_file).open("rb") as f: assert f.read() diff --git a/tests/component_djangorestframework/settings.py b/tests/component_djangorestframework/settings.py index 532153da75..e284891605 100644 --- a/tests/component_djangorestframework/settings.py +++ b/tests/component_djangorestframework/settings.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +from pathlib import Path import django -BASE_DIR = os.path.dirname(__file__) +BASE_DIR = Path(__file__).parent DEBUG = True django_version = django.VERSION diff --git a/tests/component_tastypie/settings.py b/tests/component_tastypie/settings.py index ec356809fe..01a7158781 100644 --- a/tests/component_tastypie/settings.py +++ b/tests/component_tastypie/settings.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +from pathlib import Path import django -BASE_DIR = os.path.dirname(__file__) +BASE_DIR = Path(__file__).parent DEBUG = True django_version = django.VERSION diff --git a/tests/cross_agent/test_agent_attributes.py b/tests/cross_agent/test_agent_attributes.py index 172b091a63..40f1baa36e 100644 --- a/tests/cross_agent/test_agent_attributes.py +++ b/tests/cross_agent/test_agent_attributes.py @@ -13,13 +13,11 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest -from testing_support.fixtures import override_application_settings from newrelic.core import attribute_filter as af -from newrelic.core.config import Settings, global_settings def _default_settings(): @@ -42,12 +40,11 @@ def _default_settings(): } -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE = os.path.join(CURRENT_DIR, "fixtures", "attribute_configuration.json") +FIXTURE = Path(__file__).parent / "fixtures" / "attribute_configuration.json" def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_aws_utilization_data.py b/tests/cross_agent/test_aws_utilization_data.py index 4f4b1901ac..643735e71c 100644 --- a/tests/cross_agent/test_aws_utilization_data.py +++ b/tests/cross_agent/test_aws_utilization_data.py @@ -13,7 +13,7 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest from testing_support.mock_http_client import create_client_cls @@ -21,8 +21,7 @@ from newrelic.common.utilization import AWSUtilization -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures", "utilization_vendor_specific", "aws.json")) +FIXTURE = Path(__file__).parent / "fixtures" / "utilization_vendor_specific" / "aws.json" _parameters_list = ["testname", "uri", "expected_vendors_hash", "expected_metrics"] @@ -30,7 +29,7 @@ def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_azure_utilization_data.py b/tests/cross_agent/test_azure_utilization_data.py index d0f815243c..baabd5a7b0 100644 --- a/tests/cross_agent/test_azure_utilization_data.py +++ b/tests/cross_agent/test_azure_utilization_data.py @@ -13,7 +13,7 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest from testing_support.mock_http_client import create_client_cls @@ -21,8 +21,7 @@ from newrelic.common.utilization import AzureUtilization -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures", "utilization_vendor_specific", "azure.json")) +FIXTURE = Path(__file__).parent / "fixtures" / "utilization_vendor_specific" / "azure.json" _parameters_list = ["testname", "uri", "expected_vendors_hash", "expected_metrics"] @@ -30,7 +29,7 @@ def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_boot_id_utilization_data.py b/tests/cross_agent/test_boot_id_utilization_data.py index 77b7ba780a..3d0f6dec58 100644 --- a/tests/cross_agent/test_boot_id_utilization_data.py +++ b/tests/cross_agent/test_boot_id_utilization_data.py @@ -13,18 +13,17 @@ # limitations under the License. import json -import os import sys import tempfile +from pathlib import Path import pytest from testing_support.validators.validate_internal_metrics import validate_internal_metrics from newrelic.common.system_info import BootIdUtilization -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) SYS_PLATFORM = sys.platform -FIXTURE = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures", "utilization", "boot_id.json")) +FIXTURE = Path(__file__).parent / "fixtures" / "utilization" / "boot_id.json" _parameters_list = [ "testname", @@ -40,7 +39,7 @@ def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_cat_map.py b/tests/cross_agent/test_cat_map.py index 84d4087d78..01ede0b19a 100644 --- a/tests/cross_agent/test_cat_map.py +++ b/tests/cross_agent/test_cat_map.py @@ -19,7 +19,7 @@ """ import json -import os +from pathlib import Path from urllib.request import urlopen import pytest @@ -44,8 +44,7 @@ from newrelic.common.encoding_utils import json_encode, obfuscate ENCODING_KEY = "1234567890123456789012345678901234567890" -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -JSON_DIR = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures")) +FIXTURE = Path(__file__).parent / "fixtures" / "cat_map.json" OUTBOUD_REQUESTS = {} _parameters_list = [ @@ -68,8 +67,7 @@ def server(): def load_tests(): result = [] - path = os.path.join(JSON_DIR, "cat_map.json") - with open(path) as fh: + with FIXTURE.open() as fh: tests = json.load(fh) for test in tests: diff --git a/tests/cross_agent/test_collector_hostname.py b/tests/cross_agent/test_collector_hostname.py index 815e474351..57f0654b60 100644 --- a/tests/cross_agent/test_collector_hostname.py +++ b/tests/cross_agent/test_collector_hostname.py @@ -18,18 +18,18 @@ import sys import tempfile from importlib import reload +from pathlib import Path import pytest -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures", "collector_hostname.json")) +FIXTURE = Path(__file__).parent / "fixtures" / "collector_hostname.json" _parameters_list = ["config_file_key", "config_override_host", "env_key", "env_override_host", "hostname"] _parameters = ",".join(_parameters_list) def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_datastore_instance.py b/tests/cross_agent/test_datastore_instance.py index 107ae319fc..e6e3f61c31 100644 --- a/tests/cross_agent/test_datastore_instance.py +++ b/tests/cross_agent/test_datastore_instance.py @@ -13,7 +13,7 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest @@ -23,8 +23,7 @@ from newrelic.core.database_node import DatabaseNode from newrelic.core.stats_engine import StatsEngine -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE = os.path.join(CURRENT_DIR, "fixtures", "datastores", "datastore_instances.json") +FIXTURE = Path(__file__).parent / "fixtures" / "datastores" / "datastore_instances.json" _parameters_list = [ "name", @@ -41,7 +40,7 @@ def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_distributed_tracing.py b/tests/cross_agent/test_distributed_tracing.py index 2e9c90e065..ae3097db2f 100644 --- a/tests/cross_agent/test_distributed_tracing.py +++ b/tests/cross_agent/test_distributed_tracing.py @@ -13,7 +13,7 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest import webtest @@ -28,8 +28,7 @@ from newrelic.common.encoding_utils import DistributedTracePayload from newrelic.common.object_wrapper import transient_function_wrapper -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -JSON_DIR = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures", "distributed_tracing")) +FIXTURE = Path(__file__).parent / "fixtures" / "distributed_tracing" / "distributed_tracing.json" _parameters_list = [ "account_id", @@ -53,8 +52,7 @@ def load_tests(): result = [] - path = os.path.join(JSON_DIR, "distributed_tracing.json") - with open(path) as fh: + with FIXTURE.open() as fh: tests = json.load(fh) for test in tests: diff --git a/tests/cross_agent/test_docker_container_id.py b/tests/cross_agent/test_docker_container_id.py index bd79f3e5e7..6dac502a07 100644 --- a/tests/cross_agent/test_docker_container_id.py +++ b/tests/cross_agent/test_docker_container_id.py @@ -13,14 +13,13 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest import newrelic.common.utilization as u -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -DOCKER_FIXTURE = os.path.join(CURRENT_DIR, "fixtures", "docker_container_id") +DOCKER_FIXTURE = Path(__file__).parent / "fixtures" / "docker_container_id" def _load_docker_test_attributes(): @@ -28,8 +27,8 @@ def _load_docker_test_attributes(): [(, ), ...] """ - test_cases = os.path.join(DOCKER_FIXTURE, "cases.json") - with open(test_cases) as fh: + test_cases = DOCKER_FIXTURE / "cases.json" + with test_cases.open() as fh: js = fh.read() json_list = json.loads(js) docker_test_attributes = [(json_record["filename"], json_record["containerId"]) for json_record in json_list] @@ -49,8 +48,8 @@ def _mock_open(filename, mode): @pytest.mark.parametrize("filename, containerId", _load_docker_test_attributes()) def test_docker_container_id_v1(monkeypatch, filename, containerId): - path = os.path.join(DOCKER_FIXTURE, filename) - with open(path, "rb") as f: + path = DOCKER_FIXTURE / filename + with path.open("rb") as f: monkeypatch.setattr(u, "open", mock_open(f), raising=False) if containerId is not None: assert u.DockerUtilization.detect() == {"id": containerId} diff --git a/tests/cross_agent/test_docker_container_id_v2.py b/tests/cross_agent/test_docker_container_id_v2.py index 2c7e9db464..a6ef0e9bb9 100644 --- a/tests/cross_agent/test_docker_container_id_v2.py +++ b/tests/cross_agent/test_docker_container_id_v2.py @@ -13,14 +13,13 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest import newrelic.common.utilization as u -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -DOCKER_FIXTURE = os.path.join(CURRENT_DIR, "fixtures", "docker_container_id_v2") +DOCKER_FIXTURE = Path(__file__).parent / "fixtures" / "docker_container_id_v2" def _load_docker_test_attributes(): @@ -28,8 +27,8 @@ def _load_docker_test_attributes(): [(, ), ...] """ - test_cases = os.path.join(DOCKER_FIXTURE, "cases.json") - with open(test_cases) as fh: + test_cases = DOCKER_FIXTURE / "cases.json" + with test_cases.open() as fh: js = fh.read() json_list = json.loads(js) docker_test_attributes = [(json_record["filename"], json_record["containerId"]) for json_record in json_list] @@ -49,8 +48,8 @@ def _mock_open(filename, mode): @pytest.mark.parametrize("filename, containerId", _load_docker_test_attributes()) def test_docker_container_id_v2(monkeypatch, filename, containerId): - path = os.path.join(DOCKER_FIXTURE, filename) - with open(path, "rb") as f: + path = DOCKER_FIXTURE / filename + with path.open("rb") as f: monkeypatch.setattr(u, "open", mock_open(f), raising=False) if containerId is not None: assert u.DockerUtilization.detect() == {"id": containerId} diff --git a/tests/cross_agent/test_gcp_utilization_data.py b/tests/cross_agent/test_gcp_utilization_data.py index d709036b5f..1439c92e2b 100644 --- a/tests/cross_agent/test_gcp_utilization_data.py +++ b/tests/cross_agent/test_gcp_utilization_data.py @@ -13,7 +13,7 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest from testing_support.mock_http_client import create_client_cls @@ -21,8 +21,7 @@ from newrelic.common.utilization import GCPUtilization -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures", "utilization_vendor_specific", "gcp.json")) +FIXTURE = Path(__file__).parent / "fixtures" / "utilization_vendor_specific" / "gcp.json" _parameters_list = ["testname", "uri", "expected_vendors_hash", "expected_metrics"] @@ -30,7 +29,7 @@ def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_labels_and_rollups.py b/tests/cross_agent/test_labels_and_rollups.py index 837caec96e..a16aac9c5a 100644 --- a/tests/cross_agent/test_labels_and_rollups.py +++ b/tests/cross_agent/test_labels_and_rollups.py @@ -13,20 +13,18 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest -from testing_support.fixtures import override_application_settings from newrelic.config import _map_as_mapping, _process_labels_setting from newrelic.core.config import global_settings -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE = os.path.join(CURRENT_DIR, "fixtures", "labels.json") +FIXTURE = Path(__file__).parent / "fixtures" / "labels.json" def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_lambda_event_source.py b/tests/cross_agent/test_lambda_event_source.py index 1940694879..74ca28e5d7 100644 --- a/tests/cross_agent/test_lambda_event_source.py +++ b/tests/cross_agent/test_lambda_event_source.py @@ -13,7 +13,7 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest from testing_support.fixtures import override_application_settings @@ -21,21 +21,20 @@ from newrelic.api.lambda_handler import lambda_handler -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE_DIR = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures")) -FIXTURE = os.path.join(FIXTURE_DIR, "lambda_event_source.json") +FIXTURE_DIR = Path(__file__).parent / "fixtures" +FIXTURE = FIXTURE_DIR / "lambda_event_source.json" tests = {} events = {} def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: for test in json.loads(fh.read()): test_name = test.pop("name") test_file = f"{test_name}.json" - path = os.path.join(FIXTURE_DIR, "lambda_event_source", test_file) - with open(path) as fh: + path = FIXTURE_DIR / "lambda_event_source" / test_file + with path.open() as fh: events[test_name] = json.loads(fh.read()) tests[test_name] = test diff --git a/tests/cross_agent/test_pcf_utilization_data.py b/tests/cross_agent/test_pcf_utilization_data.py index 1497f0067c..4594a55a66 100644 --- a/tests/cross_agent/test_pcf_utilization_data.py +++ b/tests/cross_agent/test_pcf_utilization_data.py @@ -13,7 +13,7 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest from testing_support.fixtures import Environ @@ -21,8 +21,7 @@ from newrelic.common.utilization import PCFUtilization -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures", "utilization_vendor_specific", "pcf.json")) +FIXTURE = Path(__file__).parent / "fixtures" / "utilization_vendor_specific" / "pcf.json" _parameters_list = ["testname", "env_vars", "expected_vendors_hash", "expected_metrics"] @@ -30,7 +29,7 @@ def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_rules.py b/tests/cross_agent/test_rules.py index 1d1668a66a..ca1eb8d354 100644 --- a/tests/cross_agent/test_rules.py +++ b/tests/cross_agent/test_rules.py @@ -13,7 +13,7 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest from testing_support.validators.validate_metric_payload import validate_metric_payload @@ -23,12 +23,11 @@ from newrelic.api.transaction import record_custom_metric from newrelic.core.rules_engine import RulesEngine -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures", "rules.json")) +FIXTURE = Path(__file__).parent / "fixtures" / "rules.json" def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_sql_obfuscation.py b/tests/cross_agent/test_sql_obfuscation.py index eb529c5472..2d166b395f 100644 --- a/tests/cross_agent/test_sql_obfuscation.py +++ b/tests/cross_agent/test_sql_obfuscation.py @@ -13,14 +13,13 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest from newrelic.core.database_utils import SQLStatement -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -JSON_DIR = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures", "sql_obfuscation")) +FIXTURE = Path(__file__).parent / "fixtures" / "sql_obfuscation" / "sql_obfuscation.json" _parameters_list = ["obfuscated", "dialects", "sql", "pathological"] _parameters = ",".join(_parameters_list) @@ -28,8 +27,7 @@ def load_tests(): result = [] - path = os.path.join(JSON_DIR, "sql_obfuscation.json") - with open(path) as fh: + with FIXTURE.open() as fh: tests = json.load(fh) for test in tests: diff --git a/tests/cross_agent/test_system_info.py b/tests/cross_agent/test_system_info.py index 15c6ca482b..f90888b9de 100644 --- a/tests/cross_agent/test_system_info.py +++ b/tests/cross_agent/test_system_info.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +from pathlib import Path import pytest @@ -67,9 +67,7 @@ def test_physical_memory_used(): ], ) def test_linux_physical_processor_count(filename, expected): - here = os.path.dirname(__file__) - path = os.path.join(here, "fixtures", "proc_cpuinfo", filename) - + path = Path(__file__).parent / "fixtures" / "proc_cpuinfo" / filename result = _linux_physical_processor_count(path) assert result == expected @@ -79,8 +77,6 @@ def test_linux_physical_processor_count(filename, expected): [("meminfo_4096MB.txt", 4194304 / 1024.0), ("malformed-file", None), ("non-existant-file.txt", None)], ) def test_linux_total_physical_memory(filename, expected): - here = os.path.dirname(__file__) - path = os.path.join(here, "fixtures", "proc_meminfo", filename) - + path = Path(__file__).parent / "fixtures" / "proc_meminfo" / filename value = _linux_total_physical_memory(path) assert value == expected diff --git a/tests/cross_agent/test_transaction_segment_terms.py b/tests/cross_agent/test_transaction_segment_terms.py index ccb0cf62c7..49dc6a19e5 100644 --- a/tests/cross_agent/test_transaction_segment_terms.py +++ b/tests/cross_agent/test_transaction_segment_terms.py @@ -15,6 +15,7 @@ import json import os from contextlib import contextmanager +from pathlib import Path import pytest @@ -23,8 +24,7 @@ from newrelic.core.agent import agent_instance from newrelic.core.rules_engine import SegmentCollapseEngine -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -JSON_DIR = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures")) +FIXTURE = Path(__file__).parent / "fixtures" / "transaction_segment_terms.json" OUTBOUD_REQUESTS = {} _parameters_list = ["testname", "transaction_segment_terms", "tests"] @@ -32,8 +32,7 @@ def load_tests(): result = [] - path = os.path.join(JSON_DIR, "transaction_segment_terms.json") - with open(path) as fh: + with FIXTURE.open() as fh: tests = json.load(fh) for test in tests: diff --git a/tests/cross_agent/test_utilization_configs.py b/tests/cross_agent/test_utilization_configs.py index 3c874d8be6..5632efa677 100644 --- a/tests/cross_agent/test_utilization_configs.py +++ b/tests/cross_agent/test_utilization_configs.py @@ -17,6 +17,7 @@ import sys import tempfile from importlib import reload +from pathlib import Path import pytest @@ -32,12 +33,11 @@ INITIAL_ENV = os.environ -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures", "utilization", "utilization_json.json")) +FIXTURE = Path(__file__).parent / "fixtures" / "utilization" / "utilization_json.json" def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: js = fh.read() return json.loads(js) diff --git a/tests/cross_agent/test_w3c_trace_context.py b/tests/cross_agent/test_w3c_trace_context.py index 125f7fcbf1..72745d3b5b 100644 --- a/tests/cross_agent/test_w3c_trace_context.py +++ b/tests/cross_agent/test_w3c_trace_context.py @@ -13,7 +13,7 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest import webtest @@ -31,8 +31,8 @@ from newrelic.common.encoding_utils import W3CTraceState from newrelic.common.object_wrapper import transient_function_wrapper -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -JSON_DIR = os.path.normpath(os.path.join(CURRENT_DIR, "fixtures", "distributed_tracing")) +FIXTURE = Path(__file__).parent / "fixtures" / "distributed_tracing" / "trace_context.json" + _parameters_list = ( "test_name", @@ -62,8 +62,7 @@ def load_tests(): result = [] - path = os.path.join(JSON_DIR, "trace_context.json") - with open(path) as fh: + with FIXTURE.open() as fh: tests = json.load(fh) for test in tests: diff --git a/tests/external_botocore/conftest.py b/tests/external_botocore/conftest.py index 2585992fd3..b5e6b7b329 100644 --- a/tests/external_botocore/conftest.py +++ b/tests/external_botocore/conftest.py @@ -16,6 +16,7 @@ import json import os import re +from pathlib import Path import pytest from _mock_external_bedrock_server import MockExternalBedrockServer, extract_shortened_prompt @@ -50,7 +51,7 @@ # Bedrock Fixtures -BEDROCK_AUDIT_LOG_FILE = os.path.join(os.path.realpath(os.path.dirname(__file__)), "bedrock_audit.log") +BEDROCK_AUDIT_LOG_FILE = Path(__file__).parent / "bedrock_audit.log" BEDROCK_AUDIT_LOG_CONTENTS = {} @@ -98,7 +99,7 @@ def bedrock_server(): # Write responses to audit log bedrock_audit_log_contents = dict(sorted(BEDROCK_AUDIT_LOG_CONTENTS.items(), key=lambda i: (i[1][1], i[0]))) - with open(BEDROCK_AUDIT_LOG_FILE, "w") as audit_log_fp: + with BEDROCK_AUDIT_LOG_FILE.open("w") as audit_log_fp: json.dump(bedrock_audit_log_contents, fp=audit_log_fp, indent=4) diff --git a/tests/external_botocore/test_s3transfer.py b/tests/external_botocore/test_s3transfer.py index bd77101c10..347fbfef6c 100644 --- a/tests/external_botocore/test_s3transfer.py +++ b/tests/external_botocore/test_s3transfer.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import uuid +from pathlib import Path import boto3 import botocore @@ -77,6 +77,6 @@ def test_s3_context_propagation(): assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200 # Upload file - test_file = os.path.join(os.path.dirname(__file__), "_test_file.txt") + test_file = Path(__file__).parent / "_test_file.txt" client.upload_file(Filename=test_file, Bucket=TEST_BUCKET, Key="_test_file.txt") # No return value to check for this function currently diff --git a/tests/external_feedparser/conftest.py b/tests/external_feedparser/conftest.py index 6493b9262d..56311bc463 100644 --- a/tests/external_feedparser/conftest.py +++ b/tests/external_feedparser/conftest.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from pathlib import Path + import pytest from testing_support.fixtures import collector_agent_registration_fixture, collector_available_fixture from testing_support.mock_external_http_server import MockExternalHTTPServer @@ -41,7 +43,7 @@ def handler(self): @pytest.fixture(scope="session") def server(): - with open("packages.xml", "rb") as f: + with Path("packages.xml").open("rb") as f: response = f.read() handler = create_handler(response) diff --git a/tests/external_feedparser/test_feedparser.py b/tests/external_feedparser/test_feedparser.py index 9dca666e56..1bf4c4c004 100644 --- a/tests/external_feedparser/test_feedparser.py +++ b/tests/external_feedparser/test_feedparser.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from pathlib import Path + import pytest from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics @@ -52,7 +54,7 @@ def test_feedparser_file(feedparser, stream, server): @background_task(name="test_feedparser_file") def _test(): if stream: - with open("packages.xml", "rb") as f: + with Path("packages.xml").open("rb") as f: feed = feedparser.parse(f) else: feed = feedparser.parse("packages.xml") diff --git a/tests/external_httplib/test_urllib.py b/tests/external_httplib/test_urllib.py index ed35002ed4..8d9dd1820d 100644 --- a/tests/external_httplib/test_urllib.py +++ b/tests/external_httplib/test_urllib.py @@ -118,9 +118,9 @@ def _test(): ) @background_task() def test_urlopener_file_request(): - filename = os.path.join("file://", __file__) + file_uri = f"file://{__file__}" opener = urllib.URLopener() - opener.open(filename) + opener.open(file_uri) @background_task() diff --git a/tests/external_httplib/test_urllib2.py b/tests/external_httplib/test_urllib2.py index b39a73f52e..54aeed7217 100644 --- a/tests/external_httplib/test_urllib2.py +++ b/tests/external_httplib/test_urllib2.py @@ -110,8 +110,7 @@ def _test(): ) @background_task() def test_urlopen_file_request(): - path = os.path.abspath(__file__) - file_uri = f"file://{path}" + file_uri = f"file://{__file__}" urllib2.urlopen(file_uri) diff --git a/tests/framework_ariadne/_target_schema_async.py b/tests/framework_ariadne/_target_schema_async.py index 0a90bc92f9..e0278d61a5 100644 --- a/tests/framework_ariadne/_target_schema_async.py +++ b/tests/framework_ariadne/_target_schema_async.py @@ -12,14 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +from pathlib import Path from ariadne import MutationType, QueryType, UnionType, load_schema_from_path, make_executable_schema from ariadne.asgi import GraphQL as GraphQLASGI from framework_graphql._target_schema_sync import books, libraries, magazines from testing_support.asgi_testing import AsgiTest -schema_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "schema.graphql") +schema_file = Path(__file__).parent / "schema.graphql" type_defs = load_schema_from_path(schema_file) storage = [] diff --git a/tests/framework_ariadne/_target_schema_sync.py b/tests/framework_ariadne/_target_schema_sync.py index ed6ead39c5..bfe91cc08e 100644 --- a/tests/framework_ariadne/_target_schema_sync.py +++ b/tests/framework_ariadne/_target_schema_sync.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +from pathlib import Path import webtest from ariadne import MutationType, QueryType, UnionType, load_schema_from_path, make_executable_schema @@ -30,7 +30,7 @@ from ariadne.asgi.graphql import GraphQL as GraphQLASGI -schema_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "schema.graphql") +schema_file = Path(__file__).parent / "schema.graphql" type_defs = load_schema_from_path(schema_file) storage = [] diff --git a/tests/framework_django/settings.py b/tests/framework_django/settings.py index 016bac5fc0..2da0c3753f 100644 --- a/tests/framework_django/settings.py +++ b/tests/framework_django/settings.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +from pathlib import Path import django -BASE_DIR = os.path.dirname(__file__) +BASE_DIR = Path(__file__).parent DEBUG = True django_version = django.VERSION @@ -43,7 +43,7 @@ ROOT_URLCONF = "urls" -TEMPLATE_DIRS = [os.path.join(BASE_DIR, "templates")] +TEMPLATE_DIRS = [BASE_DIR / "templates"] # For Django 1.10 compatibility because TEMPLATE_DIRS is deprecated TEMPLATES = [{"BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": TEMPLATE_DIRS}] diff --git a/tests/mlmodel_gemini/conftest.py b/tests/mlmodel_gemini/conftest.py index cea309d016..597bcd20c8 100644 --- a/tests/mlmodel_gemini/conftest.py +++ b/tests/mlmodel_gemini/conftest.py @@ -14,6 +14,7 @@ import json import os +from pathlib import Path import google.genai import pytest @@ -46,7 +47,7 @@ ) -GEMINI_AUDIT_LOG_FILE = os.path.join(os.path.realpath(os.path.dirname(__file__)), "gemini_audit.log") +GEMINI_AUDIT_LOG_FILE = Path(__file__).parent / "gemini_audit.log" GEMINI_AUDIT_LOG_CONTENTS = {} # Intercept outgoing requests and log to file for mocking RECORDED_HEADERS = {"content-type"} @@ -96,7 +97,7 @@ def gemini_server(gemini_clients, wrap_httpx_client_send): wrap_function_wrapper("httpx._client", "Client.send", wrap_httpx_client_send) yield # Run tests # Write responses to audit log - with open(GEMINI_AUDIT_LOG_FILE, "w") as audit_log_fp: + with GEMINI_AUDIT_LOG_FILE.open("w") as audit_log_fp: json.dump(GEMINI_AUDIT_LOG_CONTENTS, fp=audit_log_fp, indent=4) else: # We are mocking responses so we don't need to do anything in this case. diff --git a/tests/mlmodel_langchain/conftest.py b/tests/mlmodel_langchain/conftest.py index b8dcce2d21..c6d3fa7284 100644 --- a/tests/mlmodel_langchain/conftest.py +++ b/tests/mlmodel_langchain/conftest.py @@ -14,6 +14,7 @@ import json import os +from pathlib import Path import pytest from _mock_external_openai_server import ( @@ -52,7 +53,7 @@ ) -OPENAI_AUDIT_LOG_FILE = os.path.join(os.path.realpath(os.path.dirname(__file__)), "openai_audit.log") +OPENAI_AUDIT_LOG_FILE = Path(__file__).parent / "openai_audit.log" OPENAI_AUDIT_LOG_CONTENTS = {} # Intercept outgoing requests and log to file for mocking RECORDED_HEADERS = {"x-request-id", "content-type"} @@ -114,7 +115,7 @@ def openai_server( wrap_function_wrapper("openai._streaming", "Stream._iter_events", wrap_stream_iter_events) yield # Run tests # Write responses to audit log - with open(OPENAI_AUDIT_LOG_FILE, "w") as audit_log_fp: + with OPENAI_AUDIT_LOG_FILE.open("w") as audit_log_fp: json.dump(OPENAI_AUDIT_LOG_CONTENTS, fp=audit_log_fp, indent=4) else: # We are mocking openai responses so we don't need to do anything in this case. diff --git a/tests/mlmodel_langchain/new_vectorstore_adder.py b/tests/mlmodel_langchain/new_vectorstore_adder.py index a2b84d33e2..fa3b7314b9 100644 --- a/tests/mlmodel_langchain/new_vectorstore_adder.py +++ b/tests/mlmodel_langchain/new_vectorstore_adder.py @@ -20,16 +20,14 @@ copy of the newrelic-python-agent repository. """ -import os +from pathlib import Path from textwrap import dedent from langchain_community import vectorstores from newrelic.hooks.mlmodel_langchain import VECTORSTORE_CLASSES -dir_path = os.path.dirname(os.path.realpath(__file__)) -test_dir = os.path.abspath(os.path.join(dir_path, os.pardir)) -REPO_PATH = os.path.abspath(os.path.join(test_dir, os.pardir)) +REPO_PATH = Path(__file__).resolve().parent.parent.parent def add_to_config(directory, instrumented_class=None): @@ -37,7 +35,8 @@ def add_to_config(directory, instrumented_class=None): if instrumented_class: return - with open(f"{REPO_PATH}/newrelic/config.py", "r+") as file: + config_path = REPO_PATH / "newrelic" / "config.py" + with config_path.open("r+") as file: text = file.read() text = text.replace( "VectorStores with similarity_search method", @@ -58,7 +57,8 @@ def add_to_config(directory, instrumented_class=None): def add_to_hooks(class_name, directory, instrumented_class=None): - with open(f"{REPO_PATH}/newrelic/hooks/mlmodel_langchain.py", "r+") as file: + langchain_path = REPO_PATH / "newrelic" / "hooks" / "mlmodel_langchain.py" + with langchain_path.open("r+") as file: text = file.read() # The directory does not exist yet. Add the new directory and class name to the beginning of the dictionary diff --git a/tests/mlmodel_langchain/test_vectorstore.py b/tests/mlmodel_langchain/test_vectorstore.py index 2115f82ea4..8f479000b5 100644 --- a/tests/mlmodel_langchain/test_vectorstore.py +++ b/tests/mlmodel_langchain/test_vectorstore.py @@ -13,7 +13,7 @@ # limitations under the License. import copy -import os +from pathlib import Path import langchain import pytest @@ -36,6 +36,8 @@ from newrelic.api.transaction import add_custom_attribute from newrelic.common.object_names import callable_name +PDF_FILE_PATH = str(Path(__file__).parent / "hello.pdf") + def vectorstore_events_sans_content(event): new_event = copy.deepcopy(event) @@ -82,7 +84,7 @@ def vectorstore_events_sans_content(event): "metadata.page_label": "1", "metadata.page": 0, "metadata.producer": "xdvipdfmx (20210318)", - "metadata.source": os.path.join(os.path.dirname(__file__), "hello.pdf"), + "metadata.source": PDF_FILE_PATH, "metadata.total_pages": 1, }, ), @@ -143,8 +145,7 @@ def test_pdf_pagesplitter_vectorstore_in_txn(set_trace_info, embedding_openai_cl add_custom_attribute("non_llm_attr", "python-agent") with WithLlmCustomAttributes({"context": "attr"}): - script_dir = os.path.dirname(__file__) - loader = PyPDFLoader(os.path.join(script_dir, "hello.pdf")) + loader = PyPDFLoader(PDF_FILE_PATH) docs = loader.load() faiss_index = FAISS.from_documents(docs, embedding_openai_client) @@ -172,8 +173,7 @@ def test_pdf_pagesplitter_vectorstore_in_txn_no_content(set_trace_info, embeddin add_custom_attribute("llm.foo", "bar") add_custom_attribute("non_llm_attr", "python-agent") - script_dir = os.path.dirname(__file__) - loader = PyPDFLoader(os.path.join(script_dir, "hello.pdf")) + loader = PyPDFLoader(PDF_FILE_PATH) docs = loader.load() faiss_index = FAISS.from_documents(docs, embedding_openai_client) @@ -186,8 +186,7 @@ def test_pdf_pagesplitter_vectorstore_in_txn_no_content(set_trace_info, embeddin def test_pdf_pagesplitter_vectorstore_outside_txn(set_trace_info, embedding_openai_client): set_trace_info() - script_dir = os.path.dirname(__file__) - loader = PyPDFLoader(os.path.join(script_dir, "hello.pdf")) + loader = PyPDFLoader(PDF_FILE_PATH) docs = loader.load() faiss_index = FAISS.from_documents(docs, embedding_openai_client) @@ -202,8 +201,7 @@ def test_pdf_pagesplitter_vectorstore_outside_txn(set_trace_info, embedding_open def test_pdf_pagesplitter_vectorstore_ai_monitoring_disabled(set_trace_info, embedding_openai_client): set_trace_info() - script_dir = os.path.dirname(__file__) - loader = PyPDFLoader(os.path.join(script_dir, "hello.pdf")) + loader = PyPDFLoader(PDF_FILE_PATH) docs = loader.load() faiss_index = FAISS.from_documents(docs, embedding_openai_client) @@ -232,8 +230,7 @@ async def _test(): add_custom_attribute("non_llm_attr", "python-agent") with WithLlmCustomAttributes({"context": "attr"}): - script_dir = os.path.dirname(__file__) - loader = PyPDFLoader(os.path.join(script_dir, "hello.pdf")) + loader = PyPDFLoader(PDF_FILE_PATH) docs = loader.load() faiss_index = await FAISS.afrom_documents(docs, embedding_openai_client) @@ -265,8 +262,7 @@ async def _test(): add_custom_attribute("llm.foo", "bar") add_custom_attribute("non_llm_attr", "python-agent") - script_dir = os.path.dirname(__file__) - loader = PyPDFLoader(os.path.join(script_dir, "hello.pdf")) + loader = PyPDFLoader(PDF_FILE_PATH) docs = loader.load() faiss_index = await FAISS.afrom_documents(docs, embedding_openai_client) @@ -283,8 +279,7 @@ def test_async_pdf_pagesplitter_vectorstore_outside_txn(loop, set_trace_info, em async def _test(): set_trace_info() - script_dir = os.path.dirname(__file__) - loader = PyPDFLoader(os.path.join(script_dir, "hello.pdf")) + loader = PyPDFLoader(PDF_FILE_PATH) docs = loader.load() faiss_index = await FAISS.afrom_documents(docs, embedding_openai_client) @@ -302,8 +297,7 @@ def test_async_pdf_pagesplitter_vectorstore_ai_monitoring_disabled(loop, set_tra async def _test(): set_trace_info() - script_dir = os.path.dirname(__file__) - loader = PyPDFLoader(os.path.join(script_dir, "hello.pdf")) + loader = PyPDFLoader(PDF_FILE_PATH) docs = loader.load() faiss_index = await FAISS.afrom_documents(docs, embedding_openai_client) @@ -348,8 +342,8 @@ def test_vectorstore_error(set_trace_info, embedding_openai_client, loop): with pytest.raises(AssertionError): with WithLlmCustomAttributes({"context": "attr"}): set_trace_info() - script_dir = os.path.dirname(__file__) - loader = PyPDFLoader(os.path.join(script_dir, "hello.pdf")) + + loader = PyPDFLoader(PDF_FILE_PATH) docs = loader.load() faiss_index = FAISS.from_documents(docs, embedding_openai_client) @@ -373,8 +367,7 @@ def test_vectorstore_error(set_trace_info, embedding_openai_client, loop): def test_vectorstore_error_no_content(set_trace_info, embedding_openai_client): set_trace_info() with pytest.raises(AssertionError): - script_dir = os.path.dirname(__file__) - loader = PyPDFLoader(os.path.join(script_dir, "hello.pdf")) + loader = PyPDFLoader(PDF_FILE_PATH) docs = loader.load() faiss_index = FAISS.from_documents(docs, embedding_openai_client) @@ -398,8 +391,7 @@ def test_async_vectorstore_error(loop, set_trace_info, embedding_openai_client): async def _test(): set_trace_info() - script_dir = os.path.dirname(__file__) - loader = PyPDFLoader(os.path.join(script_dir, "hello.pdf")) + loader = PyPDFLoader(PDF_FILE_PATH) docs = loader.load() faiss_index = await FAISS.afrom_documents(docs, embedding_openai_client) @@ -429,8 +421,7 @@ def test_async_vectorstore_error_no_content(loop, set_trace_info, embedding_open async def _test(): set_trace_info() - script_dir = os.path.dirname(__file__) - loader = PyPDFLoader(os.path.join(script_dir, "hello.pdf")) + loader = PyPDFLoader(PDF_FILE_PATH) docs = loader.load() faiss_index = await FAISS.afrom_documents(docs, embedding_openai_client) diff --git a/tests/mlmodel_openai/conftest.py b/tests/mlmodel_openai/conftest.py index efbef78fe9..748037884f 100644 --- a/tests/mlmodel_openai/conftest.py +++ b/tests/mlmodel_openai/conftest.py @@ -14,6 +14,7 @@ import json import os +from pathlib import Path import pytest from _mock_external_openai_server import ( @@ -72,7 +73,7 @@ ] -OPENAI_AUDIT_LOG_FILE = os.path.join(os.path.realpath(os.path.dirname(__file__)), "openai_audit.log") +OPENAI_AUDIT_LOG_FILE = Path(__file__).parent / "openai_audit.log" OPENAI_AUDIT_LOG_CONTENTS = {} # Intercept outgoing requests and log to file for mocking RECORDED_HEADERS = {"x-request-id", "content-type"} @@ -163,7 +164,7 @@ def openai_server( wrap_function_wrapper("openai._streaming", "Stream._iter_events", wrap_stream_iter_events) yield # Run tests # Write responses to audit log - with open(OPENAI_AUDIT_LOG_FILE, "w") as audit_log_fp: + with OPENAI_AUDIT_LOG_FILE.open("w") as audit_log_fp: json.dump(OPENAI_AUDIT_LOG_CONTENTS, fp=audit_log_fp, indent=4) else: # We are mocking openai responses so we don't need to do anything in this case. diff --git a/tests/testing_support/certs/__init__.py b/tests/testing_support/certs/__init__.py index ae4f8e96d3..39666f1c16 100644 --- a/tests/testing_support/certs/__init__.py +++ b/tests/testing_support/certs/__init__.py @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +from pathlib import Path -CERT_PATH = os.path.join(os.path.dirname(__file__), "cert.pem") +CERT_PATH = Path(__file__).parent / "cert.pem" diff --git a/tests/testing_support/fixtures.py b/tests/testing_support/fixtures.py index 3d008c3600..ae4403a101 100644 --- a/tests/testing_support/fixtures.py +++ b/tests/testing_support/fixtures.py @@ -20,6 +20,7 @@ import sys import threading import time +from pathlib import Path from queue import Queue import pytest @@ -92,18 +93,18 @@ def initialize_agent(app_name=None, default_settings=None): env_directory = os.environ.get("TOX_ENV_DIR", None) if env_directory is not None: - log_directory = os.path.join(env_directory, "log") + log_directory = Path(env_directory) / "log" else: - log_directory = "." + log_directory = Path.cwd() - log_file = os.path.join(log_directory, "python-agent-test.log") + log_file = log_directory / "python-agent-test.log" if "GITHUB_ACTIONS" in os.environ: log_level = logging.DEBUG else: log_level = logging.INFO try: - os.unlink(log_file) + log_file.unlink() except OSError: pass @@ -227,7 +228,7 @@ def _collector_agent_registration_fixture(request): api_host = "staging-api.newrelic.com" if not use_fake_collector and not use_developer_mode: - description = os.path.basename(os.path.normpath(sys.prefix)) + description = Path(sys.prefix).resolve().name try: _logger.debug("Record deployment marker host: %s", api_host) record_deploy( From 0c57ca5f019c2678586291eb511b58e515a14da6 Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Wed, 23 Jul 2025 15:43:16 -0700 Subject: [PATCH 02/10] Sort linter list by code --- pyproject.toml | 56 +++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ae7fd88c13..dca8e9a132 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,49 +33,49 @@ pep8-naming.extend-ignore-names = ["X", "y"] select = [ # Enabled linters and rules - "E", # pycodestyle - "F", # Pyflakes - "W", # pycodestyle - "I", # isort - # "N", # pep8-naming - "UP", # pyupgrade - "YTT", # flake8-2020 + "A", # flake8-builtins "ASYNC", # flake8-async - "S", # flake8-bandit - # "BLE", # flake8-blind-except - # "FBT", # flake8-boolean-trap "B", # flake8-bugbear - "A", # flake8-builtins - "COM", # flake8-commas "C4", # flake8-comprehensions + "COM", # flake8-commas "DTZ", # flake8-datetimez - "T10", # flake8-debugger - # "EM", # flake8-errmsg + "E", # pycodestyle "EXE", # flake8-executable + "F", # Pyflakes "FA", # flake8-future-annotations - "ISC", # flake8-implicit-str-concat - "ICN", # flake8-import-conventions - "LOG", # flake8-logging + "FLY", # flynt + "FURB", # refurb "G", # flake8-logging-format + "I", # isort + "ICN", # flake8-import-conventions "INP", # flake8-no-pep420 - "PYI", # flake8-pyi + "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat + "LOG", # flake8-logging + "PERF", # Perflint + "PGH", # pygrep-hooks + "PL", # Pylint "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "PYI", # flake8-pyi "Q", # flake8-quotes "RSE", # flake8-raise - # "RET", # flake8-return + "S", # flake8-bandit "SLOT", # flake8-slots - # "SIM", # flake8-simplify + "T10", # flake8-debugger "TID", # flake8-tidy-imports - "INT", # flake8-gettext + "UP", # pyupgrade + "W", # pycodestyle + "YTT", # flake8-2020 # "ARG", # flake8-unused-arguments - "PTH", # flake8-use-pathlib - "PGH", # pygrep-hooks - "PL", # Pylint - # "TRY", # tryceratops - "FLY", # flynt - "PERF", # Perflint - "FURB", # refurb + # "BLE", # flake8-blind-except + # "EM", # flake8-errmsg + # "FBT", # flake8-boolean-trap + # "N", # pep8-naming + # "RET", # flake8-return # "RUF", # Ruff-specific rules + # "SIM", # flake8-simplify + # "TRY", # tryceratops ] # Disabled Linters From ee7c3caf05a171bf30cf1bc2b1c6090d2ef72dd8 Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Wed, 23 Jul 2025 15:44:27 -0700 Subject: [PATCH 03/10] Add ignore for new rule violation --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index dca8e9a132..b701d9c928 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,6 +109,7 @@ ignore = [ "E722", # bare-except (too many to fix all at once) "PT012", # pytest-raises-with-multiple-statements (too many to fix all at once) # Permanently disabled rules + "PLC0415", # import-outside-top-level (intentionally used frequently) "UP006", # non-pep585-annotation (not compatible with Python 3.7 or 3.8) "D203", # incorrect-blank-line-before-class "D213", # multi-line-summary-second-line From f5e49c083f377a033f21b2bcf02fc41caeb077a9 Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Wed, 23 Jul 2025 15:47:46 -0700 Subject: [PATCH 04/10] Update ruff in pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 483f9b6cb7..798b1fc441 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ default_install_hook_types: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.5 + rev: v0.12.4 hooks: # Run the linter. - id: ruff From 02c641afdb3f691eba138432e0fb247dfc7aa7ee Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Wed, 23 Jul 2025 17:36:47 -0700 Subject: [PATCH 05/10] Fix s3 tests --- tests/external_botocore/test_s3transfer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/external_botocore/test_s3transfer.py b/tests/external_botocore/test_s3transfer.py index 347fbfef6c..2172a687fc 100644 --- a/tests/external_botocore/test_s3transfer.py +++ b/tests/external_botocore/test_s3transfer.py @@ -78,5 +78,5 @@ def test_s3_context_propagation(): # Upload file test_file = Path(__file__).parent / "_test_file.txt" - client.upload_file(Filename=test_file, Bucket=TEST_BUCKET, Key="_test_file.txt") + client.upload_file(Filename=str(test_file), Bucket=TEST_BUCKET, Key="_test_file.txt") # No return value to check for this function currently From 6fe1e478f16eb9824ce9748fb09233d0e2dcfb32 Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Wed, 23 Jul 2025 17:38:27 -0700 Subject: [PATCH 06/10] Fix newer test files with pathlib changes --- tests/agent_unittests/test_aws_utilization_caching.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/agent_unittests/test_aws_utilization_caching.py b/tests/agent_unittests/test_aws_utilization_caching.py index f7f411e05b..e62eaca406 100644 --- a/tests/agent_unittests/test_aws_utilization_caching.py +++ b/tests/agent_unittests/test_aws_utilization_caching.py @@ -13,7 +13,7 @@ # limitations under the License. import json -import os +from pathlib import Path import pytest from testing_support.mock_http_client import create_client_cls @@ -21,8 +21,7 @@ from newrelic.common.utilization import AWSUtilization -CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) -FIXTURE = os.path.normpath(os.path.join(CURRENT_DIR, "aws.json")) +FIXTURE = Path(__file__).parent / "aws.json" _parameters_list = ["testname", "auth_token_cls", "uri", "expected_vendors_hash", "expected_metrics"] @@ -40,7 +39,7 @@ def no_token(cls): def _load_tests(): - with open(FIXTURE) as fh: + with FIXTURE.open() as fh: js = fh.read() return json.loads(js) From 701eb71dd32a296d4763981f88a5d00dceba2c49 Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Wed, 23 Jul 2025 17:48:39 -0700 Subject: [PATCH 07/10] Fix agent unittests --- tests/agent_unittests/test_check_environment.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/agent_unittests/test_check_environment.py b/tests/agent_unittests/test_check_environment.py index 887a132779..bda1f46de1 100644 --- a/tests/agent_unittests/test_check_environment.py +++ b/tests/agent_unittests/test_check_environment.py @@ -12,9 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import shutil import sys -import tempfile from pathlib import Path import pytest @@ -23,17 +21,17 @@ @pytest.mark.parametrize("content", [{}, {"opt": [1, 2, 3]}]) -def test_check_environment_failing(content): - temp_dir = Path(tempfile.mkdtemp()) +def test_check_environment_failing(tmp_path, content): + tmp_path = Path(tmp_path) try: - uwsgi_dir = temp_dir / "uwsgi" + uwsgi_dir = tmp_path / "uwsgi" init_file = uwsgi_dir / "__init__.py" uwsgi_dir.mkdir(parents=True) with init_file.open("w") as f: f.writelines(f"{key} = {value}" for key, value in content.items()) - sys.path.insert(0, temp_dir) + sys.path.insert(0, str(tmp_path)) import uwsgi for key, value in content.items(): @@ -41,6 +39,5 @@ def test_check_environment_failing(content): agent.check_environment() finally: - shutil.rmtree(temp_dir) - sys.path.remove(temp_dir) + sys.path.remove(str(tmp_path)) del sys.modules["uwsgi"] From 3d4c6b838ae1ec833cb7f5c1233ce1626acbb4e2 Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Wed, 23 Jul 2025 17:59:36 -0700 Subject: [PATCH 08/10] Fix path monkeypatching in docker container tests --- tests/cross_agent/test_docker_container_id.py | 8 ++++---- tests/cross_agent/test_docker_container_id_v2.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/cross_agent/test_docker_container_id.py b/tests/cross_agent/test_docker_container_id.py index 6dac502a07..a54c982c88 100644 --- a/tests/cross_agent/test_docker_container_id.py +++ b/tests/cross_agent/test_docker_container_id.py @@ -29,14 +29,14 @@ def _load_docker_test_attributes(): """ test_cases = DOCKER_FIXTURE / "cases.json" with test_cases.open() as fh: - js = fh.read() - json_list = json.loads(js) + json_list = json.load(fh) docker_test_attributes = [(json_record["filename"], json_record["containerId"]) for json_record in json_list] return docker_test_attributes def mock_open(mock_file): - def _mock_open(filename, mode): + def _mock_open(path, mode): + filename = str(path) if filename == "/proc/self/mountinfo": raise FileNotFoundError elif filename == "/proc/self/cgroup": @@ -50,7 +50,7 @@ def _mock_open(filename, mode): def test_docker_container_id_v1(monkeypatch, filename, containerId): path = DOCKER_FIXTURE / filename with path.open("rb") as f: - monkeypatch.setattr(u, "open", mock_open(f), raising=False) + monkeypatch.setattr(Path, "open", mock_open(f), raising=False) if containerId is not None: assert u.DockerUtilization.detect() == {"id": containerId} else: diff --git a/tests/cross_agent/test_docker_container_id_v2.py b/tests/cross_agent/test_docker_container_id_v2.py index a6ef0e9bb9..5603584668 100644 --- a/tests/cross_agent/test_docker_container_id_v2.py +++ b/tests/cross_agent/test_docker_container_id_v2.py @@ -29,14 +29,14 @@ def _load_docker_test_attributes(): """ test_cases = DOCKER_FIXTURE / "cases.json" with test_cases.open() as fh: - js = fh.read() - json_list = json.loads(js) + json_list = json.load(fh) docker_test_attributes = [(json_record["filename"], json_record["containerId"]) for json_record in json_list] return docker_test_attributes def mock_open(mock_file): - def _mock_open(filename, mode): + def _mock_open(path, mode): + filename = str(path) if filename == "/proc/self/cgroup": raise FileNotFoundError elif filename == "/proc/self/mountinfo": @@ -50,7 +50,7 @@ def _mock_open(filename, mode): def test_docker_container_id_v2(monkeypatch, filename, containerId): path = DOCKER_FIXTURE / filename with path.open("rb") as f: - monkeypatch.setattr(u, "open", mock_open(f), raising=False) + monkeypatch.setattr(Path, "open", mock_open(f), raising=False) if containerId is not None: assert u.DockerUtilization.detect() == {"id": containerId} else: From 731f39cbeb73d6a5d531dd73e53308b6b6c0c9ec Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Thu, 24 Jul 2025 16:30:17 -0700 Subject: [PATCH 09/10] Fix additional os.path references --- newrelic/admin/run_program.py | 5 +++-- newrelic/admin/run_python.py | 5 +++-- newrelic/bootstrap/sitecustomize.py | 5 +++-- setup.py | 12 ++++++++---- tests/agent_unittests/test_http_client.py | 1 - 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/newrelic/admin/run_program.py b/newrelic/admin/run_program.py index cf64f064f7..9ee3655350 100644 --- a/newrelic/admin/run_program.py +++ b/newrelic/admin/run_program.py @@ -50,7 +50,8 @@ def log_message(text, *args): log_message("working_directory = %r", str(Path.cwd())) log_message("current_command = %r", sys.argv) - log_message("sys.prefix = %r", os.path.normpath(sys.prefix)) + sys_prefix = str(Path(sys.prefix).resolve()) + log_message("sys.prefix = %r", sys_prefix) try: log_message("sys.real_prefix = %r", sys.real_prefix) @@ -90,7 +91,7 @@ def log_message(text, *args): os.environ["NEW_RELIC_ADMIN_COMMAND"] = repr(sys.argv) - os.environ["NEW_RELIC_PYTHON_PREFIX"] = os.path.realpath(os.path.normpath(sys.prefix)) + os.environ["NEW_RELIC_PYTHON_PREFIX"] = sys_prefix os.environ["NEW_RELIC_PYTHON_VERSION"] = ".".join(map(str, sys.version_info[:2])) # If not an absolute or relative path, then we need to diff --git a/newrelic/admin/run_python.py b/newrelic/admin/run_python.py index f9c84f233e..05892a5ccb 100644 --- a/newrelic/admin/run_python.py +++ b/newrelic/admin/run_python.py @@ -46,7 +46,8 @@ def log_message(text, *args): log_message("working_directory = %r", str(Path.cwd())) log_message("current_command = %r", sys.argv) - log_message("sys.prefix = %r", os.path.normpath(sys.prefix)) + sys_prefix = str(Path(sys.prefix).resolve()) + log_message("sys.prefix = %r", sys_prefix) try: log_message("sys.real_prefix = %r", sys.real_prefix) @@ -84,7 +85,7 @@ def log_message(text, *args): os.environ["NEW_RELIC_ADMIN_COMMAND"] = repr(sys.argv) - os.environ["NEW_RELIC_PYTHON_PREFIX"] = os.path.realpath(os.path.normpath(sys.prefix)) + os.environ["NEW_RELIC_PYTHON_PREFIX"] = sys_prefix os.environ["NEW_RELIC_PYTHON_VERSION"] = ".".join(map(str, sys.version_info[:2])) # Heroku does not set #! line on installed Python scripts diff --git a/newrelic/bootstrap/sitecustomize.py b/newrelic/bootstrap/sitecustomize.py index 3ec60afd7f..9457dc2277 100644 --- a/newrelic/bootstrap/sitecustomize.py +++ b/newrelic/bootstrap/sitecustomize.py @@ -45,7 +45,8 @@ def del_sys_path_entry(path): log_message("working_directory = %r", str(Path.cwd())) -log_message("sys.prefix = %r", os.path.normpath(sys.prefix)) +sys_prefix = str(Path(sys.prefix).resolve()) +log_message("sys.prefix = %r", sys_prefix) try: log_message("sys.real_prefix = %r", sys.real_prefix) @@ -100,7 +101,7 @@ def del_sys_path_entry(path): # which was run and only continue if we are. expected_python_prefix = os.environ.get("NEW_RELIC_PYTHON_PREFIX") -actual_python_prefix = os.path.realpath(os.path.normpath(sys.prefix)) +actual_python_prefix = sys_prefix expected_python_version = os.environ.get("NEW_RELIC_PYTHON_VERSION") actual_python_version = ".".join(map(str, sys.version_info[:2])) diff --git a/setup.py b/setup.py index 8763085189..5d8bbf7b14 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,8 @@ import os import sys +from pathlib import Path + python_version = sys.version_info[:2] if python_version >= (3, 7): @@ -81,11 +83,13 @@ def newrelic_agent_next_version(version): return version.format_next_version(newrelic_agent_guess_next_version, fmt="{guessed}") -script_directory = os.path.dirname(__file__) +script_directory = Path(__file__).parent if not script_directory: - script_directory = os.getcwd() + script_directory = Path(os.getcwd()) -readme_file = os.path.join(script_directory, "README.md") +readme_file = script_directory / "README.md" +with readme_file.open() as f: + readme_file_contents = f.read() if sys.platform == "win32" and python_version > (2, 6): build_ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, IOError) @@ -162,7 +166,7 @@ def build_extension(self, ext): }, setup_requires=["setuptools_scm>=3.2,<9"], description="New Relic Python Agent", - long_description=open(readme_file).read(), + long_description=readme_file_contents, long_description_content_type="text/markdown", url="https://docs.newrelic.com/docs/apm/agents/python-agent/", project_urls={"Source": "https://github.com/newrelic/newrelic-python-agent"}, diff --git a/tests/agent_unittests/test_http_client.py b/tests/agent_unittests/test_http_client.py index ff75f6108f..7c13406330 100644 --- a/tests/agent_unittests/test_http_client.py +++ b/tests/agent_unittests/test_http_client.py @@ -14,7 +14,6 @@ import base64 import json -import os.path import ssl import zlib from http.server import BaseHTTPRequestHandler, HTTPServer From eb8ff6c3434020fa337275c4f008f9cdcae94756 Mon Sep 17 00:00:00 2001 From: Tim Pansino Date: Thu, 24 Jul 2025 16:33:35 -0700 Subject: [PATCH 10/10] Rename all os.path.pathsep to os.pathsep for brevity --- newrelic/admin/run_program.py | 6 +++--- newrelic/admin/run_python.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/newrelic/admin/run_program.py b/newrelic/admin/run_program.py index 9ee3655350..d09c27e094 100644 --- a/newrelic/admin/run_program.py +++ b/newrelic/admin/run_program.py @@ -83,9 +83,9 @@ def log_message(text, *args): python_path = boot_directory if "PYTHONPATH" in os.environ: - path = os.environ["PYTHONPATH"].split(os.path.pathsep) + path = os.environ["PYTHONPATH"].split(os.pathsep) if boot_directory not in path: - python_path = f"{boot_directory}{os.path.pathsep}{os.environ['PYTHONPATH']}" + python_path = f"{boot_directory}{os.pathsep}{os.environ['PYTHONPATH']}" os.environ["PYTHONPATH"] = python_path @@ -102,7 +102,7 @@ def log_message(text, *args): program_exe_path = Path(args[0]) if not program_exe_path.parent: - program_search_path = os.environ.get("PATH", "").split(os.path.pathsep) + program_search_path = os.environ.get("PATH", "").split(os.pathsep) for path in program_search_path: path = Path(path) / program_exe_path if path.exists() and os.access(path, os.X_OK): diff --git a/newrelic/admin/run_python.py b/newrelic/admin/run_python.py index 05892a5ccb..411b19ee80 100644 --- a/newrelic/admin/run_python.py +++ b/newrelic/admin/run_python.py @@ -77,9 +77,9 @@ def log_message(text, *args): python_path = boot_directory if "PYTHONPATH" in os.environ: - path = os.environ["PYTHONPATH"].split(os.path.pathsep) + path = os.environ["PYTHONPATH"].split(os.pathsep) if boot_directory not in path: - python_path = f"{boot_directory}{os.path.pathsep}{os.environ['PYTHONPATH']}" + python_path = f"{boot_directory}{os.pathsep}{os.environ['PYTHONPATH']}" os.environ["PYTHONPATH"] = python_path