11from __future__ import annotations
22
3+ import dataclasses
34import os
45from collections import defaultdict
5- from pathlib import Path
6+ from dataclasses import field
67from typing import (
78 TYPE_CHECKING ,
89 Any ,
9- Dict ,
1010 Generator ,
1111 Iterator ,
12- Optional ,
1312)
1413
15- from pydantic import BaseModel , Field , validator
16-
1714from ..exceptions import InvalidPythonVersion
1815from ..utils import (
1916 KNOWN_EXTS ,
2522)
2623
2724if TYPE_CHECKING :
28- from pythonfinder .models .python import PythonVersion
29-
30-
31- class PathEntry (BaseModel ):
32- is_root : bool = Field (default = False , order = False )
33- name : Optional [str ] = None
34- path : Optional [Path ] = None
35- children_ref : Optional [Any ] = Field (default_factory = lambda : dict ())
36- only_python : Optional [bool ] = False
37- py_version_ref : Optional [Any ] = None
38- pythons_ref : Optional [Dict [Any , Any ]] = defaultdict (lambda : None )
39- is_dir_ref : Optional [bool ] = None
40- is_executable_ref : Optional [bool ] = None
41- is_python_ref : Optional [bool ] = None
42-
43- class Config :
44- validate_assignment = True
45- arbitrary_types_allowed = True
46- allow_mutation = True
47- include_private_attributes = True
48-
49- @validator ("children" , pre = True , always = True , check_fields = False )
50- def set_children (cls , v , values , ** kwargs ):
51- path = values .get ("path" )
52- if path :
53- values ["name" ] = path .name
54- return v or cls ()._gen_children ()
25+ from pathlib import Path
26+
27+ from .python import PythonVersion
28+
29+
30+ @dataclasses .dataclass (unsafe_hash = True )
31+ class PathEntry :
32+ is_root : bool = False
33+ name : str | None = None
34+ path : Path | None = None
35+ children_ref : dict [str , Any ] = field (default_factory = dict )
36+ only_python : bool | None = False
37+ py_version_ref : Any | None = None
38+ pythons_ref : dict [str , Any ] | None = field (
39+ default_factory = lambda : defaultdict (lambda : None )
40+ )
41+ is_dir_ref : bool | None = None
42+ is_executable_ref : bool | None = None
43+ is_python_ref : bool | None = None
44+
45+ def __post_init__ (self ):
46+ if not self .children_ref :
47+ self ._gen_children ()
5548
5649 def __str__ (self ) -> str :
57- return f"{ self .path . as_posix () } "
50+ return f"{ self .path } "
5851
5952 def __lt__ (self , other ) -> bool :
60- return self .path . as_posix () < other .path . as_posix ()
53+ return self .path < other .path
6154
6255 def __lte__ (self , other ) -> bool :
63- return self .path . as_posix () <= other .path . as_posix ()
56+ return self .path <= other .path
6457
6558 def __gt__ (self , other ) -> bool :
66- return self .path . as_posix () > other .path . as_posix ()
59+ return self .path > other .path
6760
6861 def __gte__ (self , other ) -> bool :
69- return self .path . as_posix () >= other .path . as_posix ()
62+ return self .path >= other .path
7063
7164 def __eq__ (self , other ) -> bool :
72- return self .path . as_posix () == other .path . as_posix ()
65+ return self .path == other .path
7366
7467 def which (self , name ) -> PathEntry | None :
7568 """Search in this path for an executable.
@@ -87,9 +80,9 @@ def which(self, name) -> PathEntry | None:
8780 if self .path is not None :
8881 found = next (
8982 (
90- children [(self .path / child ). as_posix () ]
83+ children [(self .path / child )]
9184 for child in valid_names
92- if (self .path / child ). as_posix () in children
85+ if (self .path / child ) in children
9386 ),
9487 None ,
9588 )
@@ -210,7 +203,7 @@ def pythons(self) -> dict[str | Path, PathEntry]:
210203 if not self .pythons_ref :
211204 self .pythons_ref = defaultdict (PathEntry )
212205 for python in self ._iter_pythons ():
213- python_path = python .path . as_posix ()
206+ python_path = python .path
214207 self .pythons_ref [python_path ] = python
215208 return self .pythons_ref
216209
@@ -295,17 +288,10 @@ def version_matcher(py_version):
295288 if self .is_python and self .as_python and version_matcher (self .py_version ):
296289 return self
297290
298- matching_pythons = [
299- [entry , entry .as_python .version_sort ]
300- for entry in self ._iter_pythons ()
301- if (
302- entry is not None
303- and entry .as_python is not None
304- and version_matcher (entry .py_version )
305- )
306- ]
307- results = sorted (matching_pythons , key = lambda r : (r [1 ], r [0 ]), reverse = True )
308- return next (iter (r [0 ] for r in results if r is not None ), None )
291+ for entry in self ._iter_pythons ():
292+ if entry is not None and entry .as_python is not None :
293+ if version_matcher (entry .as_python ):
294+ return entry
309295
310296 def _filter_children (self ) -> Iterator [Path ]:
311297 if not os .access (str (self .path ), os .R_OK ):
@@ -316,39 +302,26 @@ def _filter_children(self) -> Iterator[Path]:
316302 children = self .path .iterdir ()
317303 return children
318304
319- def _gen_children (self ) -> Iterator :
320- pass_name = self .name != self .path .name
321- pass_args = {"is_root" : False , "only_python" : self .only_python }
322- if pass_name :
323- if self .name is not None and isinstance (self .name , str ):
324- pass_args ["name" ] = self .name
325- elif self .path is not None and isinstance (self .path .name , str ):
326- pass_args ["name" ] = self .path .name
327-
328- if not self .is_dir :
329- yield (self .path .as_posix (), self )
330- elif self .is_root :
331- for child in self ._filter_children ():
332- if self .only_python :
333- try :
334- entry = PathEntry .create (path = child , ** pass_args )
335- except (InvalidPythonVersion , ValueError ):
336- continue
337- else :
338- try :
339- entry = PathEntry .create (path = child , ** pass_args )
340- except (InvalidPythonVersion , ValueError ):
341- continue
342- yield (child .as_posix (), entry )
343- return
305+ def _gen_children (self ):
306+ if self .is_dir and self .is_root and self .path is not None :
307+ # Assuming _filter_children returns an iterator over child paths
308+ for child_path in self ._filter_children ():
309+ pass_name = self .name != self .path .name
310+ pass_args = {"is_root" : False , "only_python" : self .only_python }
311+ if pass_name :
312+ if self .name is not None and isinstance (self .name , str ):
313+ pass_args ["name" ] = self .name
314+ elif self .path is not None and isinstance (self .path .name , str ):
315+ pass_args ["name" ] = self .path .name
316+
317+ try :
318+ entry = PathEntry .create (path = child_path , ** pass_args )
319+ self .children_ref [child_path ] = entry
320+ except (InvalidPythonVersion , ValueError ):
321+ continue # Or handle as needed
344322
345323 @property
346324 def children (self ) -> dict [str , PathEntry ]:
347- children = getattr (self , "children_ref" , {})
348- if not children :
349- for child_key , child_val in self ._gen_children ():
350- children [child_key ] = child_val
351- self .children_ref = children
352325 return self .children_ref
353326
354327 @classmethod
@@ -360,7 +333,7 @@ def create(
360333 pythons : dict [str , PythonVersion ] | None = None ,
361334 name : str | None = None ,
362335 ) -> PathEntry :
363- """Helper method for creating new :class:`pythonfinder.models. PathEntry` instances.
336+ """Helper method for creating new :class:`PathEntry` instances.
364337
365338 :param str path: Path to the specified location.
366339 :param bool is_root: Whether this is a root from the environment PATH variable, defaults to False
@@ -390,7 +363,7 @@ def create(
390363 child_creation_args ["name" ] = _new .name
391364 for pth , python in pythons .items ():
392365 pth = ensure_path (pth )
393- children [pth . as_posix ( )] = PathEntry (
366+ children [str ( path )] = PathEntry (
394367 py_version = python , path = pth , ** child_creation_args
395368 )
396369 _new .children_ref = children
0 commit comments