Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python 3.13 Support #1226

Merged
merged 10 commits into from
Oct 8, 2024
4 changes: 3 additions & 1 deletion .github/workflows/deploy-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ jobs:
- cp311-musllinux
- cp312-manylinux
- cp312-musllinux
- cp313-manylinux
- cp313-musllinux

steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # 4.1.1
Expand All @@ -48,7 +50,7 @@ jobs:
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # 3.0.0

- name: Build Wheels
uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # 2.17.0
uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # 2.21.1
env:
CIBW_PLATFORM: linux
CIBW_BUILD: "${{ matrix.wheel }}*"
Expand Down
13 changes: 0 additions & 13 deletions newrelic/common/_monotonic.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ static PyMethodDef monotonic_methods[] = {
{ NULL, NULL }
};

#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"_monotonic", /* m_name */
Expand All @@ -133,36 +132,24 @@ static struct PyModuleDef moduledef = {
NULL, /* m_clear */
NULL, /* m_free */
};
#endif

static PyObject *
moduleinit(void)
{
PyObject *module;

#if PY_MAJOR_VERSION >= 3
module = PyModule_Create(&moduledef);
#else
module = Py_InitModule3("_monotonic", monotonic_methods, NULL);
#endif

if (module == NULL)
return NULL;

return module;
}

#if PY_MAJOR_VERSION < 3
PyMODINIT_FUNC init_monotonic(void)
{
moduleinit();
}
#else
PyMODINIT_FUNC PyInit__monotonic(void)
{
return moduleinit();
}
#endif

/* ------------------------------------------------------------------------- */

21 changes: 2 additions & 19 deletions newrelic/core/_thread_utilization.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

/* ------------------------------------------------------------------------- */

#include <sys/time.h>
#include <Python.h>

#include <pythread.h>

#ifndef PyVarObject_HEAD_INIT
Expand Down Expand Up @@ -254,14 +254,10 @@ static PyObject *NRUtilization_enter(NRUtilizationObject *self, PyObject *args)
PyObject *func = NULL;

dict = PyModule_GetDict(module);
#if PY_MAJOR_VERSION >= 3
func = PyDict_GetItemString(dict, "current_thread");
#else
func = PyDict_GetItemString(dict, "currentThread");
#endif
if (func) {
Py_INCREF(func);
thread = PyEval_CallObject(func, (PyObject *)NULL);
thread = PyObject_Call(func, (PyObject *)NULL, (PyObject *)NULL);
if (!thread)
PyErr_Clear();

Expand Down Expand Up @@ -408,7 +404,6 @@ PyTypeObject NRUtilization_Type = {

/* ------------------------------------------------------------------------- */

#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"_thread_utilization", /* m_name */
Expand All @@ -420,18 +415,13 @@ static struct PyModuleDef moduledef = {
NULL, /* m_clear */
NULL, /* m_free */
};
#endif

static PyObject *
moduleinit(void)
{
PyObject *module;

#if PY_MAJOR_VERSION >= 3
module = PyModule_Create(&moduledef);
#else
module = Py_InitModule3("_thread_utilization", NULL, NULL);
#endif

if (module == NULL)
return NULL;
Expand All @@ -446,16 +436,9 @@ moduleinit(void)
return module;
}

#if PY_MAJOR_VERSION < 3
PyMODINIT_FUNC init_thread_utilization(void)
{
moduleinit();
}
#else
PyMODINIT_FUNC PyInit__thread_utilization(void)
{
return moduleinit();
}
#endif

/* ------------------------------------------------------------------------- */
6 changes: 5 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@


def newrelic_agent_guess_next_version(tag_version):
if hasattr(tag_version, "tag"): # For setuptools_scm 7.0+
tag_version = tag_version.tag

version, _, _ = str(tag_version).partition("+")
version_info = list(map(int, version.split(".")))
if len(version_info) < 3:
Expand Down Expand Up @@ -142,6 +145,7 @@ def build_extension(self, ext):
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: System :: Monitoring",
Expand All @@ -155,7 +159,7 @@ def build_extension(self, ext):
"git_describe_command": "git describe --dirty --tags --long --match *.*.*",
"write_to": "newrelic/version.txt",
},
setup_requires=["setuptools_scm>=3.2,<7"],
setup_requires=["setuptools_scm>=3.2,<9"],
description="New Relic Python Agent",
long_description=open(readme_file).read(),
url="https://docs.newrelic.com/docs/apm/agents/python-agent/",
Expand Down
17 changes: 8 additions & 9 deletions tests/agent_unittests/test_full_uri_payloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
from testing_support.fixtures import collector_agent_registration_fixture
from newrelic.core.agent_protocol import AgentProtocol
from newrelic.common.agent_http import HttpClient
from newrelic.core.config import global_settings
from newrelic.core.config import global_settings, _environ_as_bool

SKIP_IF_NO_LICENSE_KEY = pytest.mark.skipif("NEW_RELIC_LICENSE_KEY" not in os.environ, reason="License key is not expected to be valid")
TimPansino marked this conversation as resolved.
Show resolved Hide resolved
SKIP_IF_DEVELOPER_MODE = pytest.mark.skipif(_environ_as_bool("NEW_RELIC_DEVELOPER_MODE", False), reason="Cannot connect to collector in developer mode")


class FullUriClient(HttpClient):
Expand Down Expand Up @@ -55,10 +58,8 @@ def session(application):
}


@pytest.mark.skipif(
"NEW_RELIC_LICENSE_KEY" not in os.environ,
reason="License key is not expected to be valid",
)
@SKIP_IF_NO_LICENSE_KEY
@SKIP_IF_DEVELOPER_MODE
@pytest.mark.parametrize(
"method,payload",
[
Expand Down Expand Up @@ -86,10 +87,8 @@ def test_full_uri_payload(session, method, payload):
protocol.send(method, payload)


@pytest.mark.skipif(
"NEW_RELIC_LICENSE_KEY" not in os.environ,
reason="License key is not expected to be valid",
)
@SKIP_IF_NO_LICENSE_KEY
@SKIP_IF_DEVELOPER_MODE
def test_full_uri_connect():
# An exception will be raised here if there's a problem with the response
AgentProtocol.connect(
Expand Down
4 changes: 3 additions & 1 deletion tests/datastore_aiomcache/test_aiomcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@

from newrelic.api.background_task import background_task
from newrelic.api.transaction import set_background_task
from newrelic.common import system_info

DB_SETTINGS = memcached_settings()[0]

MEMCACHED_HOST = DB_SETTINGS["host"]
MEMCACHED_PORT = DB_SETTINGS["port"]
MEMCACHED_NAMESPACE = str(os.getpid())
INSTANCE_METRIC_NAME = f"Datastore/instance/Memcached/{MEMCACHED_HOST}/{MEMCACHED_PORT}"
INSTANCE_METRIC_HOST = system_info.gethostname() if MEMCACHED_HOST == "127.0.0.1" else MEMCACHED_HOST
INSTANCE_METRIC_NAME = f"Datastore/instance/Memcached/{INSTANCE_METRIC_HOST}/{MEMCACHED_PORT}"

_test_bt_set_get_delete_scoped_metrics = [
("Datastore/operation/Memcached/set", 1),
Expand Down
4 changes: 3 additions & 1 deletion tests/datastore_bmemcached/test_memcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@

from newrelic.api.background_task import background_task
from newrelic.api.transaction import set_background_task
from newrelic.common import system_info

DB_SETTINGS = memcached_settings()[0]

MEMCACHED_HOST = DB_SETTINGS["host"]
MEMCACHED_PORT = DB_SETTINGS["port"]
MEMCACHED_NAMESPACE = str(os.getpid())
MEMCACHED_ADDR = f"{MEMCACHED_HOST}:{MEMCACHED_PORT}"
INSTANCE_METRIC_NAME = f"Datastore/instance/Memcached/{MEMCACHED_HOST}/{MEMCACHED_PORT}"
INSTANCE_METRIC_HOST = system_info.gethostname() if MEMCACHED_HOST == "127.0.0.1" else MEMCACHED_HOST
INSTANCE_METRIC_NAME = f"Datastore/instance/Memcached/{INSTANCE_METRIC_HOST}/{MEMCACHED_PORT}"

_test_bt_set_get_delete_scoped_metrics = [
("Datastore/operation/Memcached/set", 1),
Expand Down
10 changes: 7 additions & 3 deletions tests/datastore_pymemcache/test_memcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,17 @@
from newrelic.api.background_task import background_task
from newrelic.api.transaction import set_background_task

from newrelic.common import system_info

DB_SETTINGS = memcached_settings()[0]

MEMCACHED_HOST = DB_SETTINGS["host"]
MEMCACHED_PORT = DB_SETTINGS["port"]
MEMCACHED_NAMESPACE = DB_SETTINGS["namespace"]

MEMCACHED_ADDR = (MEMCACHED_HOST, int(MEMCACHED_PORT))
INSTANCE_METRIC_HOST = system_info.gethostname() if MEMCACHED_HOST == "127.0.0.1" else MEMCACHED_HOST
INSTANCE_METRIC_NAME = f"Datastore/instance/Memcached/{INSTANCE_METRIC_HOST}/{MEMCACHED_PORT}"


_test_bt_set_get_delete_scoped_metrics = [
("Datastore/operation/Memcached/set", 1),
Expand All @@ -43,7 +47,7 @@
("Datastore/operation/Memcached/set", 1),
("Datastore/operation/Memcached/get", 1),
("Datastore/operation/Memcached/delete", 1),
(f"Datastore/instance/Memcached/{MEMCACHED_HOST}/{MEMCACHED_PORT}", 3),
(INSTANCE_METRIC_NAME, 3),
]


Expand Down Expand Up @@ -81,7 +85,7 @@ def test_bt_set_get_delete():
("Datastore/operation/Memcached/set", 1),
("Datastore/operation/Memcached/get", 1),
("Datastore/operation/Memcached/delete", 1),
(f"Datastore/instance/Memcached/{MEMCACHED_HOST}/{MEMCACHED_PORT}", 3),
(INSTANCE_METRIC_NAME, 3),
]


Expand Down
Loading
Loading