1
1
from __future__ import annotations
2
2
3
+ import dataclasses
3
4
import os
4
5
from collections import defaultdict
5
- from pathlib import Path
6
+ from dataclasses import field
6
7
from typing import (
7
8
TYPE_CHECKING ,
8
9
Any ,
9
- Dict ,
10
10
Generator ,
11
11
Iterator ,
12
- Optional ,
13
12
)
14
13
15
- from pydantic import BaseModel , Field , validator
16
-
17
14
from ..exceptions import InvalidPythonVersion
18
15
from ..utils import (
19
16
KNOWN_EXTS ,
25
22
)
26
23
27
24
if 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 ()
55
48
56
49
def __str__ (self ) -> str :
57
- return f"{ self .path . as_posix () } "
50
+ return f"{ self .path } "
58
51
59
52
def __lt__ (self , other ) -> bool :
60
- return self .path . as_posix () < other .path . as_posix ()
53
+ return self .path < other .path
61
54
62
55
def __lte__ (self , other ) -> bool :
63
- return self .path . as_posix () <= other .path . as_posix ()
56
+ return self .path <= other .path
64
57
65
58
def __gt__ (self , other ) -> bool :
66
- return self .path . as_posix () > other .path . as_posix ()
59
+ return self .path > other .path
67
60
68
61
def __gte__ (self , other ) -> bool :
69
- return self .path . as_posix () >= other .path . as_posix ()
62
+ return self .path >= other .path
70
63
71
64
def __eq__ (self , other ) -> bool :
72
- return self .path . as_posix () == other .path . as_posix ()
65
+ return self .path == other .path
73
66
74
67
def which (self , name ) -> PathEntry | None :
75
68
"""Search in this path for an executable.
@@ -87,9 +80,9 @@ def which(self, name) -> PathEntry | None:
87
80
if self .path is not None :
88
81
found = next (
89
82
(
90
- children [(self .path / child ). as_posix () ]
83
+ children [(self .path / child )]
91
84
for child in valid_names
92
- if (self .path / child ). as_posix () in children
85
+ if (self .path / child ) in children
93
86
),
94
87
None ,
95
88
)
@@ -210,7 +203,7 @@ def pythons(self) -> dict[str | Path, PathEntry]:
210
203
if not self .pythons_ref :
211
204
self .pythons_ref = defaultdict (PathEntry )
212
205
for python in self ._iter_pythons ():
213
- python_path = python .path . as_posix ()
206
+ python_path = python .path
214
207
self .pythons_ref [python_path ] = python
215
208
return self .pythons_ref
216
209
@@ -295,17 +288,10 @@ def version_matcher(py_version):
295
288
if self .is_python and self .as_python and version_matcher (self .py_version ):
296
289
return self
297
290
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
309
295
310
296
def _filter_children (self ) -> Iterator [Path ]:
311
297
if not os .access (str (self .path ), os .R_OK ):
@@ -316,39 +302,26 @@ def _filter_children(self) -> Iterator[Path]:
316
302
children = self .path .iterdir ()
317
303
return children
318
304
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
344
322
345
323
@property
346
324
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
352
325
return self .children_ref
353
326
354
327
@classmethod
@@ -360,7 +333,7 @@ def create(
360
333
pythons : dict [str , PythonVersion ] | None = None ,
361
334
name : str | None = None ,
362
335
) -> PathEntry :
363
- """Helper method for creating new :class:`pythonfinder.models. PathEntry` instances.
336
+ """Helper method for creating new :class:`PathEntry` instances.
364
337
365
338
:param str path: Path to the specified location.
366
339
:param bool is_root: Whether this is a root from the environment PATH variable, defaults to False
@@ -390,7 +363,7 @@ def create(
390
363
child_creation_args ["name" ] = _new .name
391
364
for pth , python in pythons .items ():
392
365
pth = ensure_path (pth )
393
- children [pth . as_posix ( )] = PathEntry (
366
+ children [str ( path )] = PathEntry (
394
367
py_version = python , path = pth , ** child_creation_args
395
368
)
396
369
_new .children_ref = children
0 commit comments