24
24
from contextlib import contextmanager
25
25
from itertools import chain
26
26
from pathlib import Path
27
- from typing import Any , Dict , List , Optional , Set , Tuple , Union
27
+ from typing import Any , Dict , List , Optional , Set , Tuple , Union , cast
28
28
29
29
import click
30
30
import yaml
36
36
from renku .core .management import RENKU_HOME
37
37
from renku .core .util .git import is_path_safe
38
38
from renku .core .util .metadata import is_external_file
39
- from renku .core .util .os import get_relative_path
39
+ from renku .core .util .os import get_absolute_path , get_relative_path , is_subpath
40
40
from renku .core .workflow .types import PATH_OBJECTS , Directory , File
41
41
from renku .domain_model .datastructures import DirectoryTree
42
42
from renku .domain_model .workflow .parameter import (
@@ -63,8 +63,8 @@ class PlanFactory:
63
63
def __init__ (
64
64
self ,
65
65
command_line : str ,
66
- explicit_inputs : Optional [List [Tuple [str , Optional [ str ] ]]] = None ,
67
- explicit_outputs : Optional [List [Tuple [str , Optional [ str ] ]]] = None ,
66
+ explicit_inputs : Optional [List [Tuple [str , str ]]] = None ,
67
+ explicit_outputs : Optional [List [Tuple [str , str ]]] = None ,
68
68
explicit_parameters : Optional [List [Tuple [str , Optional [str ]]]] = None ,
69
69
directory : Optional [str ] = None ,
70
70
working_dir : Optional [str ] = None ,
@@ -102,11 +102,11 @@ def __init__(
102
102
103
103
self .success_codes = success_codes or []
104
104
105
- self .explicit_inputs = (
106
- [(Path ( os . path . abspath ( path ) ), name ) for path , name in explicit_inputs ] if explicit_inputs else []
105
+ self .explicit_inputs : List [ Tuple [ str , str ]] = (
106
+ [(get_absolute_path ( path ), name ) for path , name in explicit_inputs ] if explicit_inputs else []
107
107
)
108
- self .explicit_outputs = (
109
- [(Path ( os . path . abspath ( path ) ), name ) for path , name in explicit_outputs ] if explicit_outputs else []
108
+ self .explicit_outputs : List [ Tuple [ str , str ]] = (
109
+ [(get_absolute_path ( path ), name ) for path , name in explicit_outputs ] if explicit_outputs else []
110
110
)
111
111
self .explicit_parameters = explicit_parameters if explicit_parameters else []
112
112
@@ -146,19 +146,16 @@ def _is_ignored_path(candidate: Union[Path, str], ignored_list: Set[str] = None)
146
146
"""Return True if the path is in ignored list."""
147
147
return ignored_list is not None and str (candidate ) in ignored_list
148
148
149
- def _resolve_existing_subpath (self , candidate ) -> Optional [Path ]:
149
+ def _resolve_existing_subpath (self , candidate : Union [ Path , str ] ) -> Optional [Path ]:
150
150
"""Return a path instance if it exists in the project's directory."""
151
- candidate = Path (candidate )
152
-
153
- if not candidate .is_absolute ():
154
- candidate = self .directory / candidate
151
+ candidate = self .directory / candidate if not os .path .isabs (candidate ) else Path (candidate )
155
152
156
153
if candidate .exists () or candidate .is_symlink ():
157
154
path = candidate .resolve ()
158
155
159
- # NOTE: If relative_path is None then it's is either an external file or an absolute path (e.g. /bin/bash)
160
- relative_path = get_relative_path ( path = path , base = self . working_dir )
161
- if relative_path is not None :
156
+ # NOTE: If resolved path is not within the project then it's is either an external file or an absolute path
157
+ # (e.g. /bin/bash )
158
+ if is_subpath ( path , base = self . working_dir ) :
162
159
return path
163
160
elif is_external_file (path = candidate , client_path = self .working_dir ):
164
161
return Path (os .path .abspath (candidate ))
@@ -324,8 +321,7 @@ def _check_potential_output_directory(
324
321
) -> Set [Tuple [str , Optional [str ]]]:
325
322
"""Check an input/parameter for being a potential output directory."""
326
323
subpaths = {str (input_path / path ) for path in tree .get (input_path , default = [])}
327
- absolute_path = os .path .abspath (input_path )
328
- if all (Path (absolute_path ) != path for path , _ in self .explicit_outputs ):
324
+ if not self ._is_explicit (input_path , self .explicit_outputs ):
329
325
content = {str (path ) for path in input_path .rglob ("*" ) if not path .is_dir () and path .name != ".gitkeep" }
330
326
preexisting_paths = content - subpaths
331
327
if preexisting_paths :
@@ -371,6 +367,7 @@ def get_stream_mapping_for_value(self, value: Any):
371
367
"""Return a stream mapping if value is a path mapped to a stream."""
372
368
if self .stdin and self .stdin == value :
373
369
return MappedIOStream (id = MappedIOStream .generate_id ("stdin" ), stream_type = "stdin" )
370
+
374
371
if self .stdout and self .stdout == value :
375
372
return MappedIOStream (id = MappedIOStream .generate_id ("stdout" ), stream_type = "stdout" )
376
373
if self .stderr and self .stderr == value :
@@ -386,7 +383,7 @@ def add_command_input(
386
383
encoding_format : Optional [List [str ]] = None ,
387
384
):
388
385
"""Create a CommandInput."""
389
- if self .no_input_detection and all ( Path ( default_value ). resolve () != path for path , _ in self .explicit_inputs ):
386
+ if self .no_input_detection and not self . _is_explicit ( default_value , self .explicit_inputs ):
390
387
return
391
388
392
389
mapped_stream = self .get_stream_mapping_for_value (default_value )
@@ -420,7 +417,7 @@ def add_command_output(
420
417
mapped_to : Optional [MappedIOStream ] = None ,
421
418
):
422
419
"""Create a CommandOutput."""
423
- if self .no_output_detection and all ( Path ( default_value ). resolve () != path for path , _ in self .explicit_outputs ):
420
+ if self .no_output_detection and not self . _is_explicit ( default_value , self .explicit_outputs ):
424
421
return
425
422
426
423
create_folder = False
@@ -478,7 +475,7 @@ def add_command_output_from_parameter(self, parameter: CommandParameter, name):
478
475
"""Create a CommandOutput from a parameter."""
479
476
self .parameters .remove (parameter )
480
477
value = Path (self ._path_relative_to_root (parameter .default_value ))
481
- encoding_format = [DIRECTORY_MIME_TYPE ] if value .resolve (). is_dir () else self ._get_mimetype (value )
478
+ encoding_format = [DIRECTORY_MIME_TYPE ] if value .is_dir () else self ._get_mimetype (value )
482
479
self .add_command_output (
483
480
default_value = str (value ),
484
481
prefix = parameter .prefix ,
@@ -512,8 +509,8 @@ def add_explicit_inputs(self):
512
509
513
510
for explicit_input , name in self .explicit_inputs :
514
511
try :
515
- relative_explicit_input = str (explicit_input . relative_to ( self .working_dir ) )
516
- except ValueError :
512
+ relative_explicit_input = get_relative_path (explicit_input , base = self .working_dir , strict = True )
513
+ except errors . ParameterError :
517
514
raise errors .UsageError (
518
515
"The input file or directory is not in the repository."
519
516
"\n \n \t " + click .style (str (explicit_input ), fg = "yellow" ) + "\n \n "
@@ -611,11 +608,15 @@ def watch(self, client_dispatcher: IClientDispatcher, no_output=False):
611
608
candidates |= {(o .b_path , None ) for o in repository .unstaged_changes if not o .deleted }
612
609
613
610
# Filter out explicit outputs
614
- explicit_output_paths = {str (path .relative_to (self .working_dir )) for path , _ in self .explicit_outputs }
611
+ explicit_output_paths = {
612
+ str (Path (path ).relative_to (self .working_dir )) for path , _ in self .explicit_outputs
613
+ }
615
614
candidates = {c for c in candidates if c [0 ] not in explicit_output_paths }
616
615
617
616
# Include explicit outputs
618
- candidates |= {(str (path .relative_to (self .working_dir )), name ) for path , name in self .explicit_outputs }
617
+ candidates |= {
618
+ (str (Path (path ).relative_to (self .working_dir )), name ) for path , name in self .explicit_outputs
619
+ }
619
620
620
621
candidates = {(path , name ) for path , name in candidates if is_path_safe (path )}
621
622
@@ -626,7 +627,7 @@ def watch(self, client_dispatcher: IClientDispatcher, no_output=False):
626
627
if (
627
628
stream
628
629
and all (stream != path for path , _ in candidates )
629
- and ( Path ( os . path . abspath (stream )) != path for path , _ in self .explicit_outputs )
630
+ and not self . _is_explicit (stream , self .explicit_outputs )
630
631
):
631
632
unmodified .add (stream )
632
633
elif stream :
@@ -652,7 +653,8 @@ def watch(self, client_dispatcher: IClientDispatcher, no_output=False):
652
653
653
654
def _path_relative_to_root (self , path ) -> str :
654
655
"""Make a potentially relative path in a subdirectory relative to the root of the repository."""
655
- return str ((self .directory / path ).resolve ().relative_to (self .working_dir ))
656
+ absolute_path = get_absolute_path (path , base = self .directory )
657
+ return cast (str , get_relative_path (absolute_path , base = self .working_dir , strict = True ))
656
658
657
659
def _include_indirect_parameters (self ):
658
660
run_parameters = read_indirect_parameters (self .working_dir )
@@ -668,7 +670,7 @@ def add_indirect_inputs(self):
668
670
669
671
for name , indirect_input in read_files_list (indirect_inputs_list ).items ():
670
672
# treat indirect inputs like explicit inputs
671
- path = Path ( os . path . abspath ( indirect_input ) )
673
+ path = get_absolute_path ( indirect_input )
672
674
self .explicit_inputs .append ((path , name ))
673
675
674
676
# add new explicit inputs (if any) to inputs
@@ -680,14 +682,19 @@ def add_indirect_outputs(self):
680
682
681
683
for name , indirect_output in read_files_list (indirect_outputs_list ).items ():
682
684
# treat indirect outputs like explicit outputs
683
- path = Path ( os . path . abspath ( indirect_output ) )
685
+ path = get_absolute_path ( indirect_output )
684
686
self .explicit_outputs .append ((path , name ))
685
687
686
688
def iter_input_files (self , basedir ):
687
689
"""Yield tuples with input id and path."""
688
690
for input_ in self .inputs :
689
691
yield input_ .id , os .path .normpath (os .path .join (basedir , input_ .default_value ))
690
692
693
+ @staticmethod
694
+ def _is_explicit (path : Union [Path , str ], explicits_collection : List [Tuple [str , str ]]) -> bool :
695
+ absolute_path = get_absolute_path (path )
696
+ return any (absolute_path == path for path , _ in explicits_collection )
697
+
691
698
@inject .autoparams ("project_gateway" )
692
699
def to_plan (
693
700
self ,
0 commit comments