Skip to content

Commit a7f271e

Browse files
authored
add python3.12 to supported pythons, add test for python 3.12 (#20)
* add python3.12 to supported pythons, add test for python 3.12 * add uv lock as pre-commit hook * relax constraints on aim for python 3.12 support * make compatible with numpy 2 * remove code coverage report post from pr pipeline * optimize AimVisBackend tests to prevent database locks. * improve patching of petrel backend in tests
1 parent 4e92b33 commit a7f271e

File tree

10 files changed

+131
-83
lines changed

10 files changed

+131
-83
lines changed

.github/workflows/pr_stage_test.yml

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,12 @@ jobs:
9898
torch: '2.4.1'
9999
cuda: 'cu118'
100100
image: 'nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04'
101-
- python-version: '3.10'
101+
- python-version: '3.12'
102102
torch: '2.8.0'
103103
cuda: 'cu129'
104104
image: 'nvidia/cuda:12.9.1-cudnn-runtime-ubuntu22.04'
105105
container:
106106
image: ${{ matrix.image }}
107-
permissions:
108-
pull-requests: write
109107
steps:
110108
- name: Show disk usage
111109
run: df -h
@@ -155,12 +153,7 @@ jobs:
155153
format: markdown
156154
output: both
157155
hide_branch_rate: false
158-
- name: Add Coverage PR Comment
159-
uses: marocchino/sticky-pull-request-comment@v2
160-
if: github.event_name == 'pull_request'
161-
with:
162-
recreate: true
163-
path: code-coverage-results.md
156+
164157

165158
# build_windows:
166159
# runs-on: windows-2022

.pre-commit-config.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,8 @@ repos:
6565
| ^docs
6666
)
6767
additional_dependencies: ["types-setuptools", "types-requests", "types-PyYAML"]
68+
- repo: https://github.com/astral-sh/uv-pre-commit
69+
# uv version.
70+
rev: 0.9.5
71+
hooks:
72+
- id: uv-lock

mmengine/config/utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ def _is_builtin_module(module_name: str) -> bool:
177177
origin_path = getattr(spec, 'origin', None)
178178
if origin_path is None:
179179
return False
180+
# Handle frozen modules (e.g., os module in some Python distributions)
181+
if origin_path == 'frozen':
182+
return True
180183
origin_path = osp.abspath(origin_path)
181184
if ('site-package' in origin_path or 'dist-package' in origin_path
182185
or not origin_path.startswith(

pyproject.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "onedl-mmengine"
3-
version = "0.10.8"
3+
version = "0.10.9"
44
description = "Engine of VBTI projects"
55
readme = "README.md"
66
authors = [
@@ -14,6 +14,8 @@ classifiers = [
1414
"Operating System :: OS Independent",
1515
"Programming Language :: Python :: 3",
1616
"Programming Language :: Python :: 3.10",
17+
"Programming Language :: Python :: 3.11",
18+
"Programming Language :: Python :: 3.12",
1719
"Topic :: Utilities"
1820
]
1921
dependencies = [
@@ -29,7 +31,7 @@ dependencies = [
2931

3032
[dependency-groups]
3133
test = [
32-
"aim<=3.17.5; sys_platform != 'win32'",
34+
"aim; sys_platform != 'win32'",
3335
"bitsandbytes",
3436
"clearml",
3537
"coverage",
@@ -77,7 +79,7 @@ all = [
7779
"termcolor",
7880
"yapf",
7981
"regex; sys_platform == 'win32'",
80-
"aim<=3.17.5; sys_platform != 'win32'",
82+
"aim; sys_platform != 'win32'",
8183
"bitsandbytes",
8284
"clearml",
8385
"coverage",

tests/data/config/lazy_module_config/test_ast_transform.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from importlib.util import find_spec as find_module
44

55
import numpy
6-
import numpy.compat
6+
import numpy.fft
77
import numpy.linalg as linalg
88

99
from mmengine.config import Config

tests/test_config/test_lazy.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from unittest import TestCase
99

1010
import numpy
11-
import numpy.compat
11+
import numpy.fft
1212
import numpy.linalg as linalg
1313
from rich.progress import Progress
1414

@@ -56,17 +56,17 @@ def test_lazy_module(self):
5656

5757
# 1.2 getattr as LazyAttr
5858
self.assertIsInstance(lazy_numpy.linalg, LazyAttr)
59-
self.assertIsInstance(lazy_numpy.compat, LazyAttr)
59+
self.assertIsInstance(lazy_numpy.fft, LazyAttr)
6060

6161
# 1.3 Build module from LazyObject. amp and functional can be accessed
6262
imported_numpy = lazy_numpy.build()
6363
self.assertIs(imported_numpy.linalg, linalg)
64-
self.assertIs(imported_numpy.compat, numpy.compat)
64+
self.assertIs(imported_numpy.fft, numpy.fft)
6565

6666
# 1.4.1 Build module from LazyAttr
6767
imported_linalg = lazy_numpy.linalg.build()
68-
imported_compat = lazy_numpy.compat.build()
69-
self.assertIs(imported_compat, numpy.compat)
68+
imported_compat = lazy_numpy.fft.build()
69+
self.assertIs(imported_compat, numpy.fft)
7070
self.assertIs(imported_linalg, linalg)
7171

7272
# 1.4.2 build class method from LazyAttr

tests/test_fileio/test_backends/test_petrel_backend.py

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Copyright (c) OpenMMLab. All rights reserved.
22
import os
33
import os.path as osp
4-
import sys
54
import tempfile
65
from contextlib import contextmanager
76
from copy import deepcopy
@@ -49,62 +48,75 @@ def build_temporary_directory():
4948
yield tmp_dir
5049

5150

51+
# Check if petrel_client is available
52+
PETREL_CLIENT_AVAILABLE = False
5253
try:
53-
# Other unit tests may mock these modules so we need to pop them first.
54-
sys.modules.pop('petrel_client', None)
55-
sys.modules.pop('petrel_client.client', None)
56-
57-
# If petrel_client is imported successfully, we can test PetrelBackend
58-
# without mock.
5954
import petrel_client # noqa: F401
60-
except ImportError:
61-
sys.modules['petrel_client'] = MagicMock()
62-
sys.modules['petrel_client.client'] = MagicMock()
63-
64-
class MockPetrelClient:
65-
66-
def __init__(self,
67-
enable_mc=True,
68-
enable_multi_cluster=False,
69-
conf_path=None):
70-
self.enable_mc = enable_mc
71-
self.enable_multi_cluster = enable_multi_cluster
72-
self.conf_path = conf_path
73-
74-
def Get(self, filepath):
75-
with open(filepath, 'rb') as f:
76-
content = f.read()
77-
return content
78-
79-
def put(self):
80-
pass
55+
PETREL_CLIENT_AVAILABLE = True
56+
except (ImportError, ModuleNotFoundError):
57+
PETREL_CLIENT_AVAILABLE = False
8158

82-
def delete(self):
83-
pass
8459

85-
def contains(self):
86-
pass
60+
class MockPetrelClient:
61+
"""Mock PetrelClient for testing when petrel_client is not available."""
8762

88-
def isdir(self):
89-
pass
63+
def __init__(self,
64+
enable_mc=True,
65+
enable_multi_cluster=False,
66+
conf_path=None):
67+
self.enable_mc = enable_mc
68+
self.enable_multi_cluster = enable_multi_cluster
69+
self.conf_path = conf_path
70+
71+
def Get(self, filepath):
72+
with open(filepath, 'rb') as f:
73+
content = f.read()
74+
return content
75+
76+
def put(self, filepath, content):
77+
pass
78+
79+
def delete(self, filepath):
80+
pass
81+
82+
def contains(self, filepath):
83+
pass
84+
85+
def isdir(self, filepath):
86+
pass
87+
88+
def list(self, dir_path):
89+
for entry in os.scandir(dir_path):
90+
if entry.name.startswith('.'):
91+
continue
92+
if entry.is_file():
93+
yield entry.name
94+
elif entry.is_dir():
95+
yield entry.name + '/'
9096

91-
def list(self, dir_path):
92-
for entry in os.scandir(dir_path):
93-
if not entry.name.startswith('.') and entry.is_file():
94-
yield entry.name
95-
elif osp.isdir(entry.path):
96-
yield entry.name + '/'
9797

98-
@contextmanager
99-
def delete_and_reset_method(obj, method):
98+
@contextmanager
99+
def delete_and_reset_method(obj, method):
100+
if hasattr(obj, '_mock_methods') or str(type(obj).__name__) == 'MagicMock':
101+
method_obj = deepcopy(getattr(obj, method))
102+
try:
103+
delattr(obj, method)
104+
yield
105+
finally:
106+
setattr(obj, method, method_obj)
107+
else:
100108
method_obj = deepcopy(getattr(type(obj), method))
101109
try:
102110
delattr(type(obj), method)
103111
yield
104112
finally:
105113
setattr(type(obj), method, method_obj)
106114

107-
@patch('petrel_client.client.Client', MockPetrelClient)
115+
116+
if not PETREL_CLIENT_AVAILABLE:
117+
# Define the test class that uses mocking when
118+
# petrel_client is not available
119+
108120
class TestPetrelBackend(TestCase):
109121

110122
@classmethod
@@ -118,6 +130,24 @@ def setUpClass(cls):
118130
cls.expected_dir = 's3://user/data'
119131
cls.expected_path = f'{cls.expected_dir}/test.jpg'
120132

133+
def setUp(self):
134+
# Mock petrel_client for each test
135+
self.mock_petrel_client = MagicMock()
136+
self.mock_client_module = MagicMock()
137+
self.mock_client_module.Client = MockPetrelClient
138+
self.mock_petrel_client.client = self.mock_client_module
139+
140+
self.patcher_petrel = patch.dict(
141+
'sys.modules', {
142+
'petrel_client': self.mock_petrel_client,
143+
'petrel_client.client': self.mock_client_module
144+
})
145+
self.patcher_petrel.start()
146+
147+
def tearDown(self):
148+
# Clean up the mock
149+
self.patcher_petrel.stop()
150+
121151
def test_name(self):
122152
backend = PetrelBackend()
123153
self.assertEqual(backend.name, 'PetrelBackend')
@@ -563,6 +593,7 @@ def test_generate_presigned_url(self):
563593
pass
564594

565595
else:
596+
# Define the test class that uses real petrel_client when available
566597

567598
class TestPetrelBackend(TestCase): # type: ignore
568599

tests/test_fileio/test_fileclient.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
from mmengine.utils import has_method
1717

1818
sys.modules['ceph'] = MagicMock()
19-
sys.modules['petrel_client'] = MagicMock()
20-
sys.modules['petrel_client.client'] = MagicMock()
2119
sys.modules['mc'] = MagicMock()
2220

2321

@@ -137,6 +135,24 @@ def setup_class(cls):
137135
cls.img_shape = (300, 400, 3)
138136
cls.text_path = cls.test_data_dir / 'filelist.txt'
139137

138+
def setup_method(self):
139+
# Mock petrel_client for each test
140+
self.mock_petrel_client = MagicMock()
141+
self.mock_client_module = MagicMock()
142+
self.mock_client_module.Client = MockPetrelClient
143+
self.mock_petrel_client.client = self.mock_client_module
144+
145+
self.patcher_petrel = patch.dict(
146+
'sys.modules', {
147+
'petrel_client': self.mock_petrel_client,
148+
'petrel_client.client': self.mock_client_module
149+
})
150+
self.patcher_petrel.start()
151+
152+
def teardown_method(self):
153+
# Clean up the mock
154+
self.patcher_petrel.stop()
155+
140156
def test_error(self):
141157
with pytest.raises(ValueError):
142158
FileClient('hadoop')
@@ -289,7 +305,6 @@ def test_disk_backend(self):
289305
osp.join('dir2', 'img.jpg'), 'text1.txt', 'text2.txt'
290306
}
291307

292-
@patch('petrel_client.client.Client', MockPetrelClient)
293308
@pytest.mark.parametrize('backend,prefix', [('petrel', None),
294309
(None, 's3')])
295310
def test_petrel_backend(self, backend, prefix):

tests/test_visualizer/test_vis_backend.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -462,37 +462,36 @@ def test_close(self):
462462
reason='Aim does not support Windows for now.')
463463
class TestAimVisBackend:
464464

465+
@classmethod
466+
def setup_class(cls):
467+
"""Setup AimVisBackend instance once for all tests in this class."""
468+
cls.aim_vis_backend = AimVisBackend()
469+
465470
def test_init(self):
466471
AimVisBackend()
467472
VISBACKENDS.build(dict(type='AimVisBackend'))
468473

469474
def test_experiment(self):
470-
aim_vis_backend = AimVisBackend()
471-
assert aim_vis_backend.experiment == aim_vis_backend._aim_run
475+
assert self.aim_vis_backend.experiment == self.aim_vis_backend._aim_run
472476

473477
def test_add_config(self):
474478
cfg = Config(dict(a=1, b=dict(b1=[0, 1])))
475-
aim_vis_backend = AimVisBackend()
476-
aim_vis_backend.add_config(cfg)
479+
self.aim_vis_backend.add_config(cfg)
477480

478481
def test_add_image(self):
479482
image = np.random.randint(0, 256, size=(10, 10, 3)).astype(np.uint8)
480-
aim_vis_backend = AimVisBackend()
481-
aim_vis_backend.add_image('img', image)
482-
aim_vis_backend.add_image('img', image, step=1)
483+
self.aim_vis_backend.add_image('img', image)
484+
self.aim_vis_backend.add_image('img', image, step=1)
483485

484486
def test_add_scalar(self):
485-
aim_vis_backend = AimVisBackend()
486-
aim_vis_backend.add_scalar('map', 0.9)
487-
aim_vis_backend.add_scalar('map', 0.9, step=1)
488-
aim_vis_backend.add_scalar('map', 0.95, step=2)
487+
self.aim_vis_backend.add_scalar('map', 0.9)
488+
self.aim_vis_backend.add_scalar('map', 0.9, step=1)
489+
self.aim_vis_backend.add_scalar('map', 0.95, step=2)
489490

490491
def test_add_scalars(self):
491-
aim_vis_backend = AimVisBackend()
492492
input_dict = {'map': 0.7, 'acc': 0.9}
493-
aim_vis_backend.add_scalars(input_dict)
493+
self.aim_vis_backend.add_scalars(input_dict)
494494

495495
def test_close(self):
496-
aim_vis_backend = AimVisBackend()
497-
aim_vis_backend._init_env()
498-
aim_vis_backend.close()
496+
self.aim_vis_backend._init_env()
497+
self.aim_vis_backend.close()

0 commit comments

Comments
 (0)