2424from contextlib import contextmanager
2525from itertools import chain
2626from 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
2828
2929import click
3030import yaml
3636from renku .core .management import RENKU_HOME
3737from renku .core .util .git import is_path_safe
3838from 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
4040from renku .core .workflow .types import PATH_OBJECTS , Directory , File
4141from renku .domain_model .datastructures import DirectoryTree
4242from renku .domain_model .workflow .parameter import (
@@ -63,8 +63,8 @@ class PlanFactory:
6363 def __init__ (
6464 self ,
6565 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 ,
6868 explicit_parameters : Optional [List [Tuple [str , Optional [str ]]]] = None ,
6969 directory : Optional [str ] = None ,
7070 working_dir : Optional [str ] = None ,
@@ -102,11 +102,11 @@ def __init__(
102102
103103 self .success_codes = success_codes or []
104104
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 []
107107 )
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 []
110110 )
111111 self .explicit_parameters = explicit_parameters if explicit_parameters else []
112112
@@ -146,19 +146,16 @@ def _is_ignored_path(candidate: Union[Path, str], ignored_list: Set[str] = None)
146146 """Return True if the path is in ignored list."""
147147 return ignored_list is not None and str (candidate ) in ignored_list
148148
149- def _resolve_existing_subpath (self , candidate ) -> Optional [Path ]:
149+ def _resolve_existing_subpath (self , candidate : Union [ Path , str ] ) -> Optional [Path ]:
150150 """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 )
155152
156153 if candidate .exists () or candidate .is_symlink ():
157154 path = candidate .resolve ()
158155
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 ) :
162159 return path
163160 elif is_external_file (path = candidate , client_path = self .working_dir ):
164161 return Path (os .path .abspath (candidate ))
@@ -324,8 +321,7 @@ def _check_potential_output_directory(
324321 ) -> Set [Tuple [str , Optional [str ]]]:
325322 """Check an input/parameter for being a potential output directory."""
326323 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 ):
329325 content = {str (path ) for path in input_path .rglob ("*" ) if not path .is_dir () and path .name != ".gitkeep" }
330326 preexisting_paths = content - subpaths
331327 if preexisting_paths :
@@ -371,6 +367,7 @@ def get_stream_mapping_for_value(self, value: Any):
371367 """Return a stream mapping if value is a path mapped to a stream."""
372368 if self .stdin and self .stdin == value :
373369 return MappedIOStream (id = MappedIOStream .generate_id ("stdin" ), stream_type = "stdin" )
370+
374371 if self .stdout and self .stdout == value :
375372 return MappedIOStream (id = MappedIOStream .generate_id ("stdout" ), stream_type = "stdout" )
376373 if self .stderr and self .stderr == value :
@@ -386,7 +383,7 @@ def add_command_input(
386383 encoding_format : Optional [List [str ]] = None ,
387384 ):
388385 """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 ):
390387 return
391388
392389 mapped_stream = self .get_stream_mapping_for_value (default_value )
@@ -420,7 +417,7 @@ def add_command_output(
420417 mapped_to : Optional [MappedIOStream ] = None ,
421418 ):
422419 """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 ):
424421 return
425422
426423 create_folder = False
@@ -478,7 +475,7 @@ def add_command_output_from_parameter(self, parameter: CommandParameter, name):
478475 """Create a CommandOutput from a parameter."""
479476 self .parameters .remove (parameter )
480477 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 )
482479 self .add_command_output (
483480 default_value = str (value ),
484481 prefix = parameter .prefix ,
@@ -512,8 +509,8 @@ def add_explicit_inputs(self):
512509
513510 for explicit_input , name in self .explicit_inputs :
514511 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 :
517514 raise errors .UsageError (
518515 "The input file or directory is not in the repository."
519516 "\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):
611608 candidates |= {(o .b_path , None ) for o in repository .unstaged_changes if not o .deleted }
612609
613610 # 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+ }
615614 candidates = {c for c in candidates if c [0 ] not in explicit_output_paths }
616615
617616 # 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+ }
619620
620621 candidates = {(path , name ) for path , name in candidates if is_path_safe (path )}
621622
@@ -626,7 +627,7 @@ def watch(self, client_dispatcher: IClientDispatcher, no_output=False):
626627 if (
627628 stream
628629 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 )
630631 ):
631632 unmodified .add (stream )
632633 elif stream :
@@ -652,7 +653,8 @@ def watch(self, client_dispatcher: IClientDispatcher, no_output=False):
652653
653654 def _path_relative_to_root (self , path ) -> str :
654655 """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 ))
656658
657659 def _include_indirect_parameters (self ):
658660 run_parameters = read_indirect_parameters (self .working_dir )
@@ -668,7 +670,7 @@ def add_indirect_inputs(self):
668670
669671 for name , indirect_input in read_files_list (indirect_inputs_list ).items ():
670672 # treat indirect inputs like explicit inputs
671- path = Path ( os . path . abspath ( indirect_input ) )
673+ path = get_absolute_path ( indirect_input )
672674 self .explicit_inputs .append ((path , name ))
673675
674676 # add new explicit inputs (if any) to inputs
@@ -680,14 +682,19 @@ def add_indirect_outputs(self):
680682
681683 for name , indirect_output in read_files_list (indirect_outputs_list ).items ():
682684 # treat indirect outputs like explicit outputs
683- path = Path ( os . path . abspath ( indirect_output ) )
685+ path = get_absolute_path ( indirect_output )
684686 self .explicit_outputs .append ((path , name ))
685687
686688 def iter_input_files (self , basedir ):
687689 """Yield tuples with input id and path."""
688690 for input_ in self .inputs :
689691 yield input_ .id , os .path .normpath (os .path .join (basedir , input_ .default_value ))
690692
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+
691698 @inject .autoparams ("project_gateway" )
692699 def to_plan (
693700 self ,
0 commit comments