11
11
import functools
12
12
import itertools
13
13
import posixpath
14
+ import contextlib
14
15
import collections
15
16
16
17
from ._compat import (
20
21
Protocol ,
21
22
)
22
23
24
+ from ._functools import method_cache
23
25
from ._itertools import unique_everseen
24
26
25
27
from configparser import ConfigParser
@@ -615,9 +617,12 @@ class FastPath:
615
617
children.
616
618
"""
617
619
620
+ @functools .lru_cache () # type: ignore
621
+ def __new__ (cls , root ):
622
+ return super ().__new__ (cls )
623
+
618
624
def __init__ (self , root ):
619
625
self .root = str (root )
620
- self .base = os .path .basename (self .root ).lower ()
621
626
622
627
def joinpath (self , child ):
623
628
return pathlib .Path (self .root , child )
@@ -637,11 +642,50 @@ def zip_children(self):
637
642
return dict .fromkeys (child .split (posixpath .sep , 1 )[0 ] for child in names )
638
643
639
644
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 ())
644
687
)
688
+ return itertools .chain (infos , eggs )
645
689
646
690
647
691
class Prepared :
@@ -650,22 +694,14 @@ class Prepared:
650
694
"""
651
695
652
696
normalized = None
653
- suffixes = 'dist-info' , 'egg-info'
654
- exact_matches = ['' ][:0 ]
655
- egg_prefix = ''
656
- versionless_egg_name = ''
697
+ legacy_normalized = None
657
698
658
699
def __init__ (self , name ):
659
700
self .name = name
660
701
if name is None :
661
702
return
662
703
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 )
669
705
670
706
@staticmethod
671
707
def normalize (name ):
@@ -682,26 +718,8 @@ def legacy_normalize(name):
682
718
"""
683
719
return name .lower ().replace ('-' , '_' )
684
720
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 )
705
723
706
724
707
725
@install
@@ -732,6 +750,9 @@ def _search_paths(cls, name, paths):
732
750
path .search (prepared ) for path in map (FastPath , paths )
733
751
)
734
752
753
+ def invalidate_caches (cls ):
754
+ FastPath .__new__ .cache_clear ()
755
+
735
756
736
757
class PathDistribution (Distribution ):
737
758
def __init__ (self , path ):
0 commit comments