3737import sys
3838from dataclasses import fields as dc_fields , is_dataclass
3939from enum import Enum
40- from typing import TYPE_CHECKING , Any , Mapping , Protocol
40+ from typing import TYPE_CHECKING , Any , Generic , Mapping , Optional , Protocol , TypeVar
4141
4242
4343if TYPE_CHECKING :
7575.. autoclass:: KeyBuilder
7676.. autoclass:: PersistentDict
7777.. autoclass:: WriteOncePersistentDict
78+
79+
80+ Internal stuff that is only here because the documentation tool wants it
81+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
82+
83+ .. class:: K
84+
85+ A type variable for the key type of a :class:`PersistentDict`.
86+
87+ .. class:: V
88+
89+ A type variable for the value type of a :class:`PersistentDict`.
7890"""
7991
8092
@@ -478,8 +490,14 @@ class CollisionWarning(UserWarning):
478490 pass
479491
480492
481- class _PersistentDictBase :
482- def __init__ (self , identifier , key_builder = None , container_dir = None ):
493+ K = TypeVar ("K" )
494+ V = TypeVar ("V" )
495+
496+
497+ class _PersistentDictBase (Generic [K , V ]):
498+ def __init__ (self , identifier : str ,
499+ key_builder : Optional [KeyBuilder ] = None ,
500+ container_dir : Optional [str ] = None ) -> None :
483501 self .identifier = identifier
484502
485503 if key_builder is None :
@@ -509,32 +527,37 @@ def __init__(self, identifier, key_builder=None, container_dir=None):
509527 self ._make_container_dir ()
510528
511529 @staticmethod
512- def _warn (msg , category = UserWarning , stacklevel = 0 ) :
530+ def _warn (msg : str , category : Any = UserWarning , stacklevel : int = 0 ) -> None :
513531 from warnings import warn
514532 warn (msg , category , stacklevel = 1 + stacklevel )
515533
516- def store_if_not_present (self , key , value , _stacklevel = 0 ):
534+ def store_if_not_present (self , key : K , value : V ,
535+ _stacklevel : int = 0 ) -> None :
536+ """Store (*key*, *value*) if *key* is not already present."""
517537 self .store (key , value , _skip_if_present = True , _stacklevel = 1 + _stacklevel )
518538
519- def store (self , key , value , _skip_if_present = False , _stacklevel = 0 ):
539+ def store (self , key : K , value : V , _skip_if_present : bool = False ,
540+ _stacklevel : int = 0 ) -> None :
541+ """Store (*key*, *value*) in the dictionary."""
520542 raise NotImplementedError ()
521543
522- def fetch (self , key , _stacklevel = 0 ):
544+ def fetch (self , key : K , _stacklevel : int = 0 ) -> V :
545+ """Return the value associated with *key* in the dictionary."""
523546 raise NotImplementedError ()
524547
525548 @staticmethod
526- def _read (path ) :
549+ def _read (path : str ) -> V :
527550 from pickle import load
528551 with open (path , "rb" ) as inf :
529552 return load (inf )
530553
531554 @staticmethod
532- def _write (path , value ) :
555+ def _write (path : str , value : V ) -> None :
533556 from pickle import HIGHEST_PROTOCOL , dump
534557 with open (path , "wb" ) as outf :
535558 dump (value , outf , protocol = HIGHEST_PROTOCOL )
536559
537- def _item_dir (self , hexdigest_key ) :
560+ def _item_dir (self , hexdigest_key : str ) -> str :
538561 from os .path import join
539562
540563 # Some file systems limit the number of directories in a directory.
@@ -546,22 +569,23 @@ def _item_dir(self, hexdigest_key):
546569 hexdigest_key [3 :6 ],
547570 hexdigest_key [6 :])
548571
549- def _key_file (self , hexdigest_key ) :
572+ def _key_file (self , hexdigest_key : str ) -> str :
550573 from os .path import join
551574 return join (self ._item_dir (hexdigest_key ), "key" )
552575
553- def _contents_file (self , hexdigest_key ) :
576+ def _contents_file (self , hexdigest_key : str ) -> str :
554577 from os .path import join
555578 return join (self ._item_dir (hexdigest_key ), "contents" )
556579
557- def _lock_file (self , hexdigest_key ) :
580+ def _lock_file (self , hexdigest_key : str ) -> str :
558581 from os .path import join
559582 return join (self .container_dir , str (hexdigest_key ) + ".lock" )
560583
561- def _make_container_dir (self ):
584+ def _make_container_dir (self ) -> None :
585+ """Create the container directory to store the dictionary."""
562586 os .makedirs (self .container_dir , exist_ok = True )
563587
564- def _collision_check (self , key , stored_key , _stacklevel ) :
588+ def _collision_check (self , key : K , stored_key : K , _stacklevel : int ) -> None :
565589 if stored_key != key :
566590 # Key collision, oh well.
567591 self ._warn (f"{ self .identifier } : key collision in cache at "
@@ -577,13 +601,16 @@ def _collision_check(self, key, stored_key, _stacklevel):
577601 stored_key == key # pylint:disable=pointless-statement # noqa: B015
578602 raise NoSuchEntryCollisionError (key )
579603
580- def __getitem__ (self , key ):
604+ def __getitem__ (self , key : K ) -> V :
605+ """Return the value associated with *key* in the dictionary."""
581606 return self .fetch (key , _stacklevel = 1 )
582607
583- def __setitem__ (self , key , value ):
608+ def __setitem__ (self , key : K , value : V ) -> None :
609+ """Store (*key*, *value*) in the dictionary."""
584610 self .store (key , value , _stacklevel = 1 )
585611
586- def clear (self ):
612+ def clear (self ) -> None :
613+ """Remove all entries from the dictionary."""
587614 try :
588615 shutil .rmtree (self .container_dir )
589616 except OSError as e :
@@ -593,8 +620,9 @@ def clear(self):
593620 self ._make_container_dir ()
594621
595622
596- class WriteOncePersistentDict (_PersistentDictBase ):
597- """A concurrent disk-backed dictionary that disallows overwriting/deletion.
623+ class WriteOncePersistentDict (_PersistentDictBase [K , V ]):
624+ """A concurrent disk-backed dictionary that disallows overwriting/
625+ deletion (but allows removing all entries).
598626
599627 Compared with :class:`PersistentDict`, this class has faster
600628 retrieval times because it uses an LRU cache to cache entries in memory.
@@ -608,14 +636,19 @@ class WriteOncePersistentDict(_PersistentDictBase):
608636 .. automethod:: store_if_not_present
609637 .. automethod:: fetch
610638 """
611- def __init__ (self , identifier , key_builder = None , container_dir = None ,
612- in_mem_cache_size = 256 ):
639+ def __init__ (self , identifier : str ,
640+ key_builder : Optional [KeyBuilder ] = None ,
641+ container_dir : Optional [str ] = None ,
642+ in_mem_cache_size : int = 256 ) -> None :
613643 """
614644 :arg identifier: a file-name-compatible string identifying this
615645 dictionary
616646 :arg key_builder: a subclass of :class:`KeyBuilder`
647+ :arg container_dir: the directory in which to store this
648+ dictionary. If ``None``, the default cache directory from
649+ :func:`platformdirs.user_cache_dir` is used
617650 :arg in_mem_cache_size: retain an in-memory cache of up to
618- *in_mem_cache_size* items
651+ *in_mem_cache_size* items (with an LRU replacement policy)
619652 """
620653 _PersistentDictBase .__init__ (self , identifier , key_builder , container_dir )
621654 self ._in_mem_cache_size = in_mem_cache_size
@@ -624,12 +657,14 @@ def __init__(self, identifier, key_builder=None, container_dir=None,
624657
625658 def clear_in_mem_cache (self ) -> None :
626659 """
660+ Clear the in-memory cache of this dictionary.
661+
627662 .. versionadded:: 2023.1.1
628663 """
629664
630665 self ._fetch .cache_clear ()
631666
632- def _spin_until_removed (self , lock_file , stacklevel ) :
667+ def _spin_until_removed (self , lock_file : str , stacklevel : int ) -> None :
633668 from os .path import exists
634669
635670 attempts = 0
@@ -649,7 +684,8 @@ def _spin_until_removed(self, lock_file, stacklevel):
649684 f"on the lock file '{ lock_file } '"
650685 "--something is wrong" )
651686
652- def store (self , key , value , _skip_if_present = False , _stacklevel = 0 ):
687+ def store (self , key : K , value : V , _skip_if_present : bool = False ,
688+ _stacklevel : int = 0 ) -> None :
653689 hexdigest_key = self .key_builder (key )
654690
655691 cleanup_m = CleanupManager ()
@@ -682,7 +718,7 @@ def store(self, key, value, _skip_if_present=False, _stacklevel=0):
682718 finally :
683719 cleanup_m .clean_up ()
684720
685- def fetch (self , key , _stacklevel = 0 ) :
721+ def fetch (self , key : K , _stacklevel : int = 0 ) -> Any :
686722 hexdigest_key = self .key_builder (key )
687723
688724 (stored_key , stored_value ) = self ._fetch (hexdigest_key , 1 + _stacklevel )
@@ -691,7 +727,8 @@ def fetch(self, key, _stacklevel=0):
691727
692728 return stored_value
693729
694- def _fetch (self , hexdigest_key , _stacklevel = 0 ): # pylint:disable=method-hidden
730+ def _fetch (self , hexdigest_key : str , # pylint:disable=method-hidden
731+ _stacklevel : int = 0 ) -> V :
695732 # This is separate from fetch() to allow for LRU caching
696733
697734 # {{{ check path exists and is unlocked
@@ -748,12 +785,12 @@ def _fetch(self, hexdigest_key, _stacklevel=0): # pylint:disable=method-hidden
748785
749786 return (read_key , read_contents )
750787
751- def clear (self ):
788+ def clear (self ) -> None :
752789 _PersistentDictBase .clear (self )
753790 self ._fetch .cache_clear ()
754791
755792
756- class PersistentDict (_PersistentDictBase ):
793+ class PersistentDict (_PersistentDictBase [ K , V ] ):
757794 """A concurrent disk-backed dictionary.
758795
759796 .. automethod:: __init__
@@ -766,15 +803,22 @@ class PersistentDict(_PersistentDictBase):
766803 .. automethod:: fetch
767804 .. automethod:: remove
768805 """
769- def __init__ (self , identifier , key_builder = None , container_dir = None ):
806+ def __init__ (self ,
807+ identifier : str ,
808+ key_builder : Optional [KeyBuilder ] = None ,
809+ container_dir : Optional [str ] = None ) -> None :
770810 """
771811 :arg identifier: a file-name-compatible string identifying this
772812 dictionary
773813 :arg key_builder: a subclass of :class:`KeyBuilder`
814+ :arg container_dir: the directory in which to store this
815+ dictionary. If ``None``, the default cache directory from
816+ :func:`platformdirs.user_cache_dir` is used
774817 """
775818 _PersistentDictBase .__init__ (self , identifier , key_builder , container_dir )
776819
777- def store (self , key , value , _skip_if_present = False , _stacklevel = 0 ):
820+ def store (self , key : K , value : V , _skip_if_present : bool = False ,
821+ _stacklevel : int = 0 ) -> None :
778822 hexdigest_key = self .key_builder (key )
779823
780824 cleanup_m = CleanupManager ()
@@ -807,7 +851,7 @@ def store(self, key, value, _skip_if_present=False, _stacklevel=0):
807851 finally :
808852 cleanup_m .clean_up ()
809853
810- def fetch (self , key , _stacklevel = 0 ) :
854+ def fetch (self , key : K , _stacklevel : int = 0 ) -> V :
811855 hexdigest_key = self .key_builder (key )
812856 item_dir = self ._item_dir (hexdigest_key )
813857
@@ -871,7 +915,8 @@ def fetch(self, key, _stacklevel=0):
871915 finally :
872916 cleanup_m .clean_up ()
873917
874- def remove (self , key , _stacklevel = 0 ):
918+ def remove (self , key : K , _stacklevel : int = 0 ) -> None :
919+ """Remove the entry associated with *key* from the dictionary."""
875920 hexdigest_key = self .key_builder (key )
876921
877922 item_dir = self ._item_dir (hexdigest_key )
@@ -913,7 +958,8 @@ def remove(self, key, _stacklevel=0):
913958 finally :
914959 cleanup_m .clean_up ()
915960
916- def __delitem__ (self , key ):
961+ def __delitem__ (self , key : K ) -> None :
962+ """Remove the entry associated with *key* from the dictionary."""
917963 self .remove (key , _stacklevel = 1 )
918964
919965# }}}
0 commit comments