1111import functools
1212import itertools
1313import posixpath
14+ import contextlib
1415import collections
1516
1617from ._compat import (
2021 Protocol ,
2122)
2223
24+ from ._functools import method_cache
2325from ._itertools import unique_everseen
2426
2527from configparser import ConfigParser
@@ -615,9 +617,12 @@ class FastPath:
615617 children.
616618 """
617619
620+ @functools .lru_cache () # type: ignore
621+ def __new__ (cls , root ):
622+ return super ().__new__ (cls )
623+
618624 def __init__ (self , root ):
619625 self .root = str (root )
620- self .base = os .path .basename (self .root ).lower ()
621626
622627 def joinpath (self , child ):
623628 return pathlib .Path (self .root , child )
@@ -637,11 +642,50 @@ def zip_children(self):
637642 return dict .fromkeys (child .split (posixpath .sep , 1 )[0 ] for child in names )
638643
639644 def search (self , name ):
640- return (
641- self .joinpath (child )
642- for child in self .children ()
643- if name .matches (child , self .base )
645+ return self .lookup (self .mtime ).search (name )
646+
647+ @property
648+ def mtime (self ):
649+ with contextlib .suppress (OSError ):
650+ return os .stat (self .root ).st_mtime
651+ self .lookup .cache_clear ()
652+
653+ @method_cache
654+ def lookup (self , mtime ):
655+ return Lookup (self )
656+
657+
658+ class Lookup :
659+ def __init__ (self , path : FastPath ):
660+ base = os .path .basename (path .root ).lower ()
661+ base_is_egg = base .endswith (".egg" )
662+ self .infos = collections .defaultdict (list )
663+ self .eggs = collections .defaultdict (list )
664+
665+ for child in path .children ():
666+ low = child .lower ()
667+ if low .endswith ((".dist-info" , ".egg-info" )):
668+ # rpartition is faster than splitext and suitable for this purpose.
669+ name = low .rpartition ("." )[0 ].partition ("-" )[0 ]
670+ normalized = Prepared .normalize (name )
671+ self .infos [normalized ].append (path .joinpath (child ))
672+ elif base_is_egg and low == "egg-info" :
673+ name = base .rpartition ("." )[0 ].partition ("-" )[0 ]
674+ legacy_normalized = Prepared .legacy_normalize (name )
675+ self .eggs [legacy_normalized ].append (path .joinpath (child ))
676+
677+ def search (self , prepared ):
678+ infos = (
679+ self .infos [prepared .normalized ]
680+ if prepared
681+ else itertools .chain .from_iterable (self .infos .values ())
682+ )
683+ eggs = (
684+ self .eggs [prepared .legacy_normalized ]
685+ if prepared
686+ else itertools .chain .from_iterable (self .eggs .values ())
644687 )
688+ return itertools .chain (infos , eggs )
645689
646690
647691class Prepared :
@@ -650,22 +694,14 @@ class Prepared:
650694 """
651695
652696 normalized = None
653- suffixes = 'dist-info' , 'egg-info'
654- exact_matches = ['' ][:0 ]
655- egg_prefix = ''
656- versionless_egg_name = ''
697+ legacy_normalized = None
657698
658699 def __init__ (self , name ):
659700 self .name = name
660701 if name is None :
661702 return
662703 self .normalized = self .normalize (name )
663- self .exact_matches = [
664- self .normalized + '.' + suffix for suffix in self .suffixes
665- ]
666- legacy_normalized = self .legacy_normalize (self .name )
667- self .egg_prefix = legacy_normalized + '-'
668- self .versionless_egg_name = legacy_normalized + '.egg'
704+ self .legacy_normalized = self .legacy_normalize (name )
669705
670706 @staticmethod
671707 def normalize (name ):
@@ -682,26 +718,8 @@ def legacy_normalize(name):
682718 """
683719 return name .lower ().replace ('-' , '_' )
684720
685- def matches (self , cand , base ):
686- low = cand .lower ()
687- # rpartition is faster than splitext and suitable for this purpose.
688- pre , _ , ext = low .rpartition ('.' )
689- name , _ , rest = pre .partition ('-' )
690- return (
691- low in self .exact_matches
692- or ext in self .suffixes
693- and (not self .normalized or name .replace ('.' , '_' ) == self .normalized )
694- # legacy case:
695- or self .is_egg (base )
696- and low == 'egg-info'
697- )
698-
699- def is_egg (self , base ):
700- return (
701- base == self .versionless_egg_name
702- or base .startswith (self .egg_prefix )
703- and base .endswith ('.egg' )
704- )
721+ def __bool__ (self ):
722+ return bool (self .name )
705723
706724
707725@install
@@ -732,6 +750,9 @@ def _search_paths(cls, name, paths):
732750 path .search (prepared ) for path in map (FastPath , paths )
733751 )
734752
753+ def invalidate_caches (cls ):
754+ FastPath .__new__ .cache_clear ()
755+
735756
736757class PathDistribution (Distribution ):
737758 def __init__ (self , path ):
0 commit comments