Skip to content

Commit

Permalink
Make singleton class instance dict unique per subclass (#318)
Browse files Browse the repository at this point in the history
Co-authored-by: Bernát Gábor <gaborjbernat@gmail.com>
  • Loading branch information
nefrob and gaborbernat committed Mar 25, 2024
1 parent 9a64375 commit 3f6df70
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 2 deletions.
9 changes: 7 additions & 2 deletions src/filelock/_api.py
Expand Up @@ -8,7 +8,7 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from threading import local
from typing import TYPE_CHECKING, Any, ClassVar
from typing import TYPE_CHECKING, Any
from weakref import WeakValueDictionary

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

_instances: ClassVar[WeakValueDictionary[str, BaseFileLock]] = WeakValueDictionary()
_instances: WeakValueDictionary[str, BaseFileLock]

def __new__( # noqa: PLR0913
cls,
Expand All @@ -100,6 +100,11 @@ def __new__( # noqa: PLR0913

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

def __init_subclass__(cls, **kwargs: dict[str, Any]) -> None:
"""Setup unique state for lock subclasses."""
super().__init_subclass__(**kwargs)
cls._instances = WeakValueDictionary()

def __init__( # noqa: PLR0913
self,
lock_file: str | os.PathLike[str],
Expand Down
15 changes: 15 additions & 0 deletions tests/test_filelock.py
Expand Up @@ -14,6 +14,7 @@
from types import TracebackType
from typing import TYPE_CHECKING, Any, Callable, Iterator, Tuple, Type, Union
from uuid import uuid4
from weakref import WeakValueDictionary

import pytest

Expand Down Expand Up @@ -687,3 +688,17 @@ def test_singleton_locks_are_deleted_when_no_external_references_exist(
assert lock_type._instances == {str(lock_path): lock} # noqa: SLF001
del lock
assert lock_type._instances == {} # noqa: SLF001


@pytest.mark.skipif(hasattr(sys, "pypy_version_info"), reason="del() does not trigger GC in PyPy")
@pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock])
def test_singleton_instance_tracking_is_unique_per_subclass(lock_type: type[BaseFileLock]) -> None:
class Lock1(lock_type): # type: ignore[valid-type, misc]
pass

class Lock2(lock_type): # type: ignore[valid-type, misc]
pass

assert isinstance(Lock1._instances, WeakValueDictionary) # noqa: SLF001
assert isinstance(Lock2._instances, WeakValueDictionary) # noqa: SLF001
assert Lock1._instances is not Lock2._instances # noqa: SLF001

0 comments on commit 3f6df70

Please sign in to comment.