Skip to content

Commit 3a001b0

Browse files
authored
Python 3.13 Support (#1226)
* Add Python 3.13 tests to tox * Fix memcache tests * Fix agent unittests under developer mode * Add wheels for 3.13 * Replace PyEval_CallObject with PyObject_Call in C extensions * Remove Python 2 specific code for C extensions * Update includes for C extensions for Py 3.13 compatibility * Add support for setuptools_scm v7 and v8 * Address feedback * Adding additional tox envs that are now supported
1 parent 1807857 commit 3a001b0

File tree

9 files changed

+127
-127
lines changed

9 files changed

+127
-127
lines changed

.github/workflows/deploy-python.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ jobs:
3737
- cp311-musllinux
3838
- cp312-manylinux
3939
- cp312-musllinux
40+
- cp313-manylinux
41+
- cp313-musllinux
4042

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

5052
- name: Build Wheels
51-
uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # 2.17.0
53+
uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # 2.21.1
5254
env:
5355
CIBW_PLATFORM: linux
5456
CIBW_BUILD: "${{ matrix.wheel }}*"

newrelic/common/_monotonic.c

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ static PyMethodDef monotonic_methods[] = {
121121
{ NULL, NULL }
122122
};
123123

124-
#if PY_MAJOR_VERSION >= 3
125124
static struct PyModuleDef moduledef = {
126125
PyModuleDef_HEAD_INIT,
127126
"_monotonic", /* m_name */
@@ -133,36 +132,24 @@ static struct PyModuleDef moduledef = {
133132
NULL, /* m_clear */
134133
NULL, /* m_free */
135134
};
136-
#endif
137135

138136
static PyObject *
139137
moduleinit(void)
140138
{
141139
PyObject *module;
142140

143-
#if PY_MAJOR_VERSION >= 3
144141
module = PyModule_Create(&moduledef);
145-
#else
146-
module = Py_InitModule3("_monotonic", monotonic_methods, NULL);
147-
#endif
148142

149143
if (module == NULL)
150144
return NULL;
151145

152146
return module;
153147
}
154148

155-
#if PY_MAJOR_VERSION < 3
156-
PyMODINIT_FUNC init_monotonic(void)
157-
{
158-
moduleinit();
159-
}
160-
#else
161149
PyMODINIT_FUNC PyInit__monotonic(void)
162150
{
163151
return moduleinit();
164152
}
165-
#endif
166153

167154
/* ------------------------------------------------------------------------- */
168155

newrelic/core/_thread_utilization.c

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
/* ------------------------------------------------------------------------- */
1818

19+
#include <sys/time.h>
1920
#include <Python.h>
20-
2121
#include <pythread.h>
2222

2323
#ifndef PyVarObject_HEAD_INIT
@@ -254,14 +254,10 @@ static PyObject *NRUtilization_enter(NRUtilizationObject *self, PyObject *args)
254254
PyObject *func = NULL;
255255

256256
dict = PyModule_GetDict(module);
257-
#if PY_MAJOR_VERSION >= 3
258257
func = PyDict_GetItemString(dict, "current_thread");
259-
#else
260-
func = PyDict_GetItemString(dict, "currentThread");
261-
#endif
262258
if (func) {
263259
Py_INCREF(func);
264-
thread = PyEval_CallObject(func, (PyObject *)NULL);
260+
thread = PyObject_Call(func, (PyObject *)NULL, (PyObject *)NULL);
265261
if (!thread)
266262
PyErr_Clear();
267263

@@ -408,7 +404,6 @@ PyTypeObject NRUtilization_Type = {
408404

409405
/* ------------------------------------------------------------------------- */
410406

411-
#if PY_MAJOR_VERSION >= 3
412407
static struct PyModuleDef moduledef = {
413408
PyModuleDef_HEAD_INIT,
414409
"_thread_utilization", /* m_name */
@@ -420,18 +415,13 @@ static struct PyModuleDef moduledef = {
420415
NULL, /* m_clear */
421416
NULL, /* m_free */
422417
};
423-
#endif
424418

425419
static PyObject *
426420
moduleinit(void)
427421
{
428422
PyObject *module;
429423

430-
#if PY_MAJOR_VERSION >= 3
431424
module = PyModule_Create(&moduledef);
432-
#else
433-
module = Py_InitModule3("_thread_utilization", NULL, NULL);
434-
#endif
435425

436426
if (module == NULL)
437427
return NULL;
@@ -446,16 +436,9 @@ moduleinit(void)
446436
return module;
447437
}
448438

449-
#if PY_MAJOR_VERSION < 3
450-
PyMODINIT_FUNC init_thread_utilization(void)
451-
{
452-
moduleinit();
453-
}
454-
#else
455439
PyMODINIT_FUNC PyInit__thread_utilization(void)
456440
{
457441
return moduleinit();
458442
}
459-
#endif
460443

461444
/* ------------------------------------------------------------------------- */

setup.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161

6262

6363
def newrelic_agent_guess_next_version(tag_version):
64+
if hasattr(tag_version, "tag"): # For setuptools_scm 7.0+
65+
tag_version = tag_version.tag
66+
6467
version, _, _ = str(tag_version).partition("+")
6568
version_info = list(map(int, version.split(".")))
6669
if len(version_info) < 3:
@@ -142,6 +145,7 @@ def build_extension(self, ext):
142145
"Programming Language :: Python :: 3.10",
143146
"Programming Language :: Python :: 3.11",
144147
"Programming Language :: Python :: 3.12",
148+
"Programming Language :: Python :: 3.13",
145149
"Programming Language :: Python :: Implementation :: CPython",
146150
"Programming Language :: Python :: Implementation :: PyPy",
147151
"Topic :: System :: Monitoring",
@@ -155,7 +159,7 @@ def build_extension(self, ext):
155159
"git_describe_command": "git describe --dirty --tags --long --match *.*.*",
156160
"write_to": "newrelic/version.txt",
157161
},
158-
setup_requires=["setuptools_scm>=3.2,<7"],
162+
setup_requires=["setuptools_scm>=3.2,<9"],
159163
description="New Relic Python Agent",
160164
long_description=open(readme_file).read(),
161165
url="https://docs.newrelic.com/docs/apm/agents/python-agent/",

tests/agent_unittests/test_full_uri_payloads.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
from testing_support.fixtures import collector_agent_registration_fixture
2121
from newrelic.core.agent_protocol import AgentProtocol
2222
from newrelic.common.agent_http import HttpClient
23-
from newrelic.core.config import global_settings
23+
from newrelic.core.config import global_settings, _environ_as_bool
24+
25+
DEVELOPER_MODE = _environ_as_bool("NEW_RELIC_DEVELOPER_MODE", False) or "NEW_RELIC_LICENSE_KEY" not in os.environ
26+
SKIP_IF_DEVELOPER_MODE = pytest.mark.skipif(DEVELOPER_MODE, reason="Cannot connect to collector in developer mode")
2427

2528

2629
class FullUriClient(HttpClient):
@@ -55,10 +58,7 @@ def session(application):
5558
}
5659

5760

58-
@pytest.mark.skipif(
59-
"NEW_RELIC_LICENSE_KEY" not in os.environ,
60-
reason="License key is not expected to be valid",
61-
)
61+
@SKIP_IF_DEVELOPER_MODE
6262
@pytest.mark.parametrize(
6363
"method,payload",
6464
[
@@ -86,10 +86,7 @@ def test_full_uri_payload(session, method, payload):
8686
protocol.send(method, payload)
8787

8888

89-
@pytest.mark.skipif(
90-
"NEW_RELIC_LICENSE_KEY" not in os.environ,
91-
reason="License key is not expected to be valid",
92-
)
89+
@SKIP_IF_DEVELOPER_MODE
9390
def test_full_uri_connect():
9491
# An exception will be raised here if there's a problem with the response
9592
AgentProtocol.connect(

tests/datastore_aiomcache/test_aiomcache.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@
2323

2424
from newrelic.api.background_task import background_task
2525
from newrelic.api.transaction import set_background_task
26+
from newrelic.common import system_info
2627

2728
DB_SETTINGS = memcached_settings()[0]
2829

2930
MEMCACHED_HOST = DB_SETTINGS["host"]
3031
MEMCACHED_PORT = DB_SETTINGS["port"]
3132
MEMCACHED_NAMESPACE = str(os.getpid())
32-
INSTANCE_METRIC_NAME = f"Datastore/instance/Memcached/{MEMCACHED_HOST}/{MEMCACHED_PORT}"
33+
INSTANCE_METRIC_HOST = system_info.gethostname() if MEMCACHED_HOST == "127.0.0.1" else MEMCACHED_HOST
34+
INSTANCE_METRIC_NAME = f"Datastore/instance/Memcached/{INSTANCE_METRIC_HOST}/{MEMCACHED_PORT}"
3335

3436
_test_bt_set_get_delete_scoped_metrics = [
3537
("Datastore/operation/Memcached/set", 1),

tests/datastore_bmemcached/test_memcache.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@
2222

2323
from newrelic.api.background_task import background_task
2424
from newrelic.api.transaction import set_background_task
25+
from newrelic.common import system_info
2526

2627
DB_SETTINGS = memcached_settings()[0]
2728

2829
MEMCACHED_HOST = DB_SETTINGS["host"]
2930
MEMCACHED_PORT = DB_SETTINGS["port"]
3031
MEMCACHED_NAMESPACE = str(os.getpid())
3132
MEMCACHED_ADDR = f"{MEMCACHED_HOST}:{MEMCACHED_PORT}"
32-
INSTANCE_METRIC_NAME = f"Datastore/instance/Memcached/{MEMCACHED_HOST}/{MEMCACHED_PORT}"
33+
INSTANCE_METRIC_HOST = system_info.gethostname() if MEMCACHED_HOST == "127.0.0.1" else MEMCACHED_HOST
34+
INSTANCE_METRIC_NAME = f"Datastore/instance/Memcached/{INSTANCE_METRIC_HOST}/{MEMCACHED_PORT}"
3335

3436
_test_bt_set_get_delete_scoped_metrics = [
3537
("Datastore/operation/Memcached/set", 1),

tests/datastore_pymemcache/test_memcache.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,17 @@
2121
from newrelic.api.background_task import background_task
2222
from newrelic.api.transaction import set_background_task
2323

24+
from newrelic.common import system_info
25+
2426
DB_SETTINGS = memcached_settings()[0]
2527

2628
MEMCACHED_HOST = DB_SETTINGS["host"]
2729
MEMCACHED_PORT = DB_SETTINGS["port"]
2830
MEMCACHED_NAMESPACE = DB_SETTINGS["namespace"]
29-
3031
MEMCACHED_ADDR = (MEMCACHED_HOST, int(MEMCACHED_PORT))
32+
INSTANCE_METRIC_HOST = system_info.gethostname() if MEMCACHED_HOST == "127.0.0.1" else MEMCACHED_HOST
33+
INSTANCE_METRIC_NAME = f"Datastore/instance/Memcached/{INSTANCE_METRIC_HOST}/{MEMCACHED_PORT}"
34+
3135

3236
_test_bt_set_get_delete_scoped_metrics = [
3337
("Datastore/operation/Memcached/set", 1),
@@ -43,7 +47,7 @@
4347
("Datastore/operation/Memcached/set", 1),
4448
("Datastore/operation/Memcached/get", 1),
4549
("Datastore/operation/Memcached/delete", 1),
46-
(f"Datastore/instance/Memcached/{MEMCACHED_HOST}/{MEMCACHED_PORT}", 3),
50+
(INSTANCE_METRIC_NAME, 3),
4751
]
4852

4953

@@ -81,7 +85,7 @@ def test_bt_set_get_delete():
8185
("Datastore/operation/Memcached/set", 1),
8286
("Datastore/operation/Memcached/get", 1),
8387
("Datastore/operation/Memcached/delete", 1),
84-
(f"Datastore/instance/Memcached/{MEMCACHED_HOST}/{MEMCACHED_PORT}", 3),
88+
(INSTANCE_METRIC_NAME, 3),
8589
]
8690

8791

0 commit comments

Comments
 (0)