Skip to content

Commit 3f6df70

Browse files
nefrobgaborbernat
andauthored
Make singleton class instance dict unique per subclass (#318)
Co-authored-by: Bernát Gábor <[email protected]>
1 parent 9a64375 commit 3f6df70

File tree

2 files changed

+22
-2
lines changed

2 files changed

+22
-2
lines changed

src/filelock/_api.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from abc import ABC, abstractmethod
99
from dataclasses import dataclass
1010
from threading import local
11-
from typing import TYPE_CHECKING, Any, ClassVar
11+
from typing import TYPE_CHECKING, Any
1212
from weakref import WeakValueDictionary
1313

1414
from ._error import Timeout
@@ -77,7 +77,7 @@ class ThreadLocalFileContext(FileLockContext, local):
7777
class BaseFileLock(ABC, contextlib.ContextDecorator):
7878
"""Abstract base class for a file lock object."""
7979

80-
_instances: ClassVar[WeakValueDictionary[str, BaseFileLock]] = WeakValueDictionary()
80+
_instances: WeakValueDictionary[str, BaseFileLock]
8181

8282
def __new__( # noqa: PLR0913
8383
cls,
@@ -100,6 +100,11 @@ def __new__( # noqa: PLR0913
100100

101101
return instance # type: ignore[return-value] # https://github.com/python/mypy/issues/15322
102102

103+
def __init_subclass__(cls, **kwargs: dict[str, Any]) -> None:
104+
"""Setup unique state for lock subclasses."""
105+
super().__init_subclass__(**kwargs)
106+
cls._instances = WeakValueDictionary()
107+
103108
def __init__( # noqa: PLR0913
104109
self,
105110
lock_file: str | os.PathLike[str],

tests/test_filelock.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from types import TracebackType
1515
from typing import TYPE_CHECKING, Any, Callable, Iterator, Tuple, Type, Union
1616
from uuid import uuid4
17+
from weakref import WeakValueDictionary
1718

1819
import pytest
1920

@@ -687,3 +688,17 @@ def test_singleton_locks_are_deleted_when_no_external_references_exist(
687688
assert lock_type._instances == {str(lock_path): lock} # noqa: SLF001
688689
del lock
689690
assert lock_type._instances == {} # noqa: SLF001
691+
692+
693+
@pytest.mark.skipif(hasattr(sys, "pypy_version_info"), reason="del() does not trigger GC in PyPy")
694+
@pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock])
695+
def test_singleton_instance_tracking_is_unique_per_subclass(lock_type: type[BaseFileLock]) -> None:
696+
class Lock1(lock_type): # type: ignore[valid-type, misc]
697+
pass
698+
699+
class Lock2(lock_type): # type: ignore[valid-type, misc]
700+
pass
701+
702+
assert isinstance(Lock1._instances, WeakValueDictionary) # noqa: SLF001
703+
assert isinstance(Lock2._instances, WeakValueDictionary) # noqa: SLF001
704+
assert Lock1._instances is not Lock2._instances # noqa: SLF001

0 commit comments

Comments
 (0)