
    gS                     \   S r SSKJr  SSKJr  SSKJr  SSKrSSKrSSKrSSKrSSK	r	SSK
r
SSKrSSKJr  SSKJr  SSKJr  SS	KJr  SS
KJr  SSKJr  SSKrSSKJr  Sr " S S\R4                  5      r " S S\5      r " S S\5      r " S S\5      r " S S\5      r " S S\5      r  " S S\5      r! " S S\5      r"S r#S r$\RJ                  S  5       r& " S! S"\RN                  " \RP                  \)5      5      r* " S# S$\*5      r+ " S% S&\*5      r,S' r-S( r.S) r/S* r0S-S+ jr1S.S, jr2g)/zUtilities for job submission preparation.

The main entry point is UploadPythonPackages, which takes in parameters derived
from the command line arguments and returns a list of URLs to be given to the
AI Platform API. See its docstring for details.
    )absolute_import)division)unicode_literalsN)storage_util)uploads)
exceptions)execution_utils)log)files)mapzfrom setuptools import setup, find_packages

if __name__ == '__main__':
    setup(
        name='{package_name}',
        packages=find_packages(include=['{package_name}'])
    )
c                       \ rS rSrSrSrg)UploadFailureError7   z0Generic error with the packaging/upload process. N__name__
__module____qualname____firstlineno____doc____static_attributes__r       5lib/googlecloudsdk/command_lib/ml_engine/jobs_prep.pyr   r   7   s    8r   r   c                   ,   ^  \ rS rSrSrU 4S jrSrU =r$ )SetuptoolsFailedError<   z/Error indicating that setuptools itself failed.c                 r   > SR                  U5      nU(       a  US-  nOUS-  n[        [        U ]  U5        g )Nz8Packaging of user Python code failed with message:

{}

zTTry manually writing a setup.py file at your package root and rerunning the command.zTry manually building your Python code by running:
  $ python setup.py sdist
and providing the output via the `--packages` flag (for example, `--packages dist/package.tar.gz,dist/package2.whl)`)formatsuperr   __init__)selfoutput	generatedmsg	__class__s       r   r    SetuptoolsFailedError.__init__?   sM    VF^ 	 ' (c 
 M Nc 

/4r   r   r   r   r   r   r   r    r   __classcell__r%   s   @r   r   r   <   s    75 5r   r   c                   ,   ^  \ rS rSrSrU 4S jrSrU =r$ )SysExecutableMissingErrorM   z/Error indicating that sys.executable was empty.c                 T   > [         [        U ]  [        R                  " S5      5        g )Nz        No Python executable found on path. A Python executable with setuptools
        installed on the PYTHONPATH is required for building AI Platform training jobs.
        )r   r+   r    textwrapdedent)r!   r%   s    r   r    "SysExecutableMissingError.__init__P   s$    	
#T3  	r   r   r'   r)   s   @r   r+   r+   M   s    7 r   r+   c                   ,   ^  \ rS rSrSrU 4S jrSrU =r$ )MissingInitErrorX   zCError indicating that the package to build had no __init__.py file.c                 r   > [         [        U ]  [        R                  " S5      R                  U5      5        g )Nz        [{}] is not a valid Python package because it does not contain an         `__init__.py` file. Please create one and try again. Also, please         ensure that --package-path refers to a local directory.
        )r   r2   r    r.   r/   r   )r!   package_dirr%   s     r   r    MissingInitError.__init__[   s/    	
D*8?? < , VK 	"r   r   r'   r)   s   @r   r2   r2   X   s    K" "r   r2   c                       \ rS rSrSrSrg)UncopyablePackageErrorc   zError with copying the package.r   Nr   r   r   r   r8   r8   c   s    'r   r8   c                   ,   ^  \ rS rSrSrU 4S jrSrU =r$ )DuplicateEntriesErrorg   zFError indicating that multiple files with the same name were provided.c                 h   > [         [        U ]  SR                  SR	                  U5      5      5        g )Nz<Cannot upload multiple packages with the same filename: [{}], )r   r;   r    r   join)r!   
duplicatesr%   s     r   r    DuplicateEntriesError.__init__j   s-    	
/FMMIIj!	#$r   r   r'   r)   s   @r   r;   r;   g   s    N$ $r   r;   c                       \ rS rSrSrSrg)NoStagingLocationErrorp   z6No staging location was provided but one was required.r   Nr   r   r   r   rC   rC   p   s    >r   rC   c                   ,   ^  \ rS rSrSrU 4S jrSrU =r$ )InvalidSourceDirErrort   z6Error indicating that the source directory is invalid.c                 J   > [         [        U ]  SR                  U5      5        g )Nz/Source directory [{}] is not a valid directory.)r   rF   r    r   )r!   
source_dirr%   s     r   r    InvalidSourceDirError.__init__w   s"    	
/9@@LNr   r   r'   r)   s   @r   rF   rF   t   s    >N Nr   rF   c                 ,   [         R                  R                  U 5      (       d  [        U 5      e [        R
                  " U 5      nU(       a  U $ [        R                  " X5      (       a  [        SR                  X5      5      e[         R                  R                  US5      n[        R                  " SX5         [        R                  " X5        U$ ! [         a    [        U 5      ef = f! [         a    [        SR                  U5      5      ef = f)aX  Returns a writable directory with the same contents as source_dir.

If source_dir is writable, it is used. Otherwise, a directory 'dest' inside of
temp_dir is used.

Args:
  source_dir: str, the directory to (potentially) copy
  temp_dir: str, the path to a writable temporary directory in which to store
    any copied code.

Returns:
  str, the path to a writable directory with the same contents as source_dir
    (i.e. source_dir, if it's writable, or a copy otherwise).

Raises:
  UploadFailureError: if the command exits non-zero.
  InvalidSourceDirError: if the source directory is not valid.
zVCannot copy directory since working directory [{}] is inside of source directory [{}].destz+Copying local source tree from [%s] to [%s]z%Cannot write to working location [{}])ospathisdirrF   r   HasWriteAccessInDir
ValueErrorIsDirAncestorOfr8   r   r?   r
   debugCopyTreeOSError)rI   temp_dirwritabledest_dirs       r   _CopyIfNotWritablerY   |   s    & 
z	"	"


++,((4H 
:00
 	!!'!=? ? WW\\(F+())9:PB	NN:( 
/% 
 ,


++, 
 B
 /66x@B BBs   C =C. C+.%Dc                 *   [         R                  " SU 5        [        R                  R	                  U 5      (       a  [         R
                  " SU 5        g[        R                  US9n[         R
                  " SU5        [        R                  " X5        g)aL  Generates a temporary setup.py file if there is none at the given path.

Args:
  setup_py_path: str, a path to the expected setup.py location.
  package_name: str, the name of the Python package for which to write a
    setup.py file (used in the generated file contents).

Returns:
  bool, whether the setup.py file was generated.
z!Looking for setup.py file at [%s]z$Using existing setup.py file at [%s]F)package_namez&Generating temporary setup.py file:
%sT)
r
   rS   rM   rN   isfileinfoDEFAULT_SETUP_FILEr   r   WriteFileContents)setup_py_pathr[   setup_contentss      r   _GenerateSetupPyIfNeededrb      sk     ))/?WW^^M""HH3]C%,,,,G.((4nE-8	r   c              #   <  #     [         R                  " 5       nUR                  5       n Uv   U(       a#  UR                  " [
        R                  " 5       6   gg! [         a    SnU n N@f = f! U(       a#  UR                  " [
        R                  " 5       6   f f = f7f)av  Yields a temporary directory or a backup temporary directory.

Prefers creating a temporary directory (which will be cleaned up when the
context manager is closed), but falls back to default_dir. There are systems
where users can't write to temp, but we still need to copy.

Args:
  default_dir: str, the backup temporary directory.

Yields:
  str, the temporary directory.
N)r   TemporaryDirectory	__enter__rU   __exit__sysexc_info)default_dirrV   rN   s      r   _TempDirOrBackuprj      s     
'')H
 D)
J(  
 HD ( s8   B%A A- +BA*'B)A**B-,BBc                   l    \ rS rSrSrS r\R                  S 5       r\R                  S 5       r	S r
Srg)	_SetupPyCommand   a  A command to run setup.py in a given environment.

Includes the Python version to use and the arguments with which to run
setup.py.

Attributes:
  setup_py_path: str, the path to the setup.py file
  setup_py_args: list of str, the arguments with which to call setup.py
  package_root: str, path to the directory containing the package to build
    (must be writable, or setuptools will fail)
c                 (    Xl         X l        X0l        g N)r`   setup_py_argspackage_root)r!   r`   rp   rq   s       r   r    _SetupPyCommand.__init__   s    &&$r   c                     [        5       e)zEReturns arguments to use for execution (including Python executable).NotImplementedErrorr!   s    r   GetArgs_SetupPyCommand.GetArgs        
r   c                     [        5       e)z?Returns the environment dictionary to use for Python execution.rt   rv   s    r   GetEnv_SetupPyCommand.GetEnv   ry   r   c           	          [         R                  " U R                  5       SUR                  UR                  U R                  U R                  5       S9$ )zRun the configured setup.py command.

Args:
  out: a stream to which the command output should be written.

Returns:
  int, the return code of the command.
T)no_exitout_funcerr_funccwdenv)r	   Execrw   writerq   r{   )r!   outs     r   Execute_SetupPyCommand.Execute   s@     syy3994;;=2 2r   )rq   rp   r`   N)r   r   r   r   r   r    abcabstractmethodrw   r{   r   r   r   r   r   rl   rl      sE    
%
        2r   rl   c                   $    \ rS rSrSrS rS rSrg)_CloudSdkPythonSetupPyCommandi  zA command that uses the Cloud SDK Python environment.

It uses the same OS environment, plus the same PYTHONPATH.

This is preferred, since it's more controlled.
c                 n    [         R                  " U R                  /U R                  Q7S[	        5       06$ )Npython)r	   ArgsForPythonToolr`   rp   GetPythonExecutablerv   s    r   rw   %_CloudSdkPythonSetupPyCommand.GetArgs  s:    ,,T-?-? K.2.@.@K4G4IK Kr   c                     [         R                  R                  5       n[         R                  R	                  [
        R                  5      US'   U$ )N
PYTHONPATH)rM   environcopypathsepr?   rg   rN   )r!   exec_envs     r   r{   $_CloudSdkPythonSetupPyCommand.GetEnv  s2    zz HZZ__SXX6H\Or   r   Nr   r   r   r   r   rw   r{   r   r   r   r   r   r     s    K
r   r   c                   $    \ rS rSrSrS rS rSrg)_SystemPythonSetupPyCommandi  zA command that uses the system Python environment.

Uses the same executable as the Cloud SDK.

Important in case of e.g. a setup.py file that has non-stdlib dependencies.
c                 H    [        5       U R                  /U R                  -   $ ro   )r   r`   rp   rv   s    r   rw   #_SystemPythonSetupPyCommand.GetArgs'  s!    !4#5#569K9KKKr   c                     g ro   r   rv   s    r   r{   "_SystemPythonSetupPyCommand.GetEnv*  s    r   r   Nr   r   r   r   r   r     s    Lr   r   c                  f    S n  [         R                  " 5       n U $ ! [         a    [        5       ef = fro   )r	   r   rQ   r+   )python_executables    r   r   r   .  s>    &';;= 
 
 &
#
%%&s    0c           	         [        U 5       nSSU/nSSUSU/nSSU/nXe-   U-   XT-   U4n/ nU H9  n	UR                  [        XU 5      5        UR                  [        XU 5      5        M;     U H2  n
[        R
                  " 5       nU
R                  U5      nU(       a  M2    O   [        WR                  5       5      e SSS5        [        R                  " U5       Vs/ s H"  n[        R                  R                  X-5      PM$     nn[        R                  " S	S
R                  U5      5        U$ ! , (       d  f       Ny= fs  snf )a  Executes the setuptools `sdist` command.

Specifically, runs `python setup.py sdist` (with the full path to `setup.py`
given by setup_py_path) with arguments to put the final output in output_dir
and all possible temporary files in a temporary directory. package_root is
used as the working directory.

May attempt to run setup.py multiple times with different
environments/commands if any execution fails:

1. Using the Cloud SDK Python environment, with a full setuptools invocation
   (`egg_info`, `build`, and `sdist`).
2. Using the system Python environment, with a full setuptools invocation
   (`egg_info`, `build`, and `sdist`).
3. Using the Cloud SDK Python environment, with an intermediate setuptools
   invocation (`build` and `sdist`).
4. Using the system Python environment, with an intermediate setuptools
   invocation (`build` and `sdist`).
5. Using the Cloud SDK Python environment, with a simple setuptools
   invocation which will also work for plain distutils-based setup.py (just
   `sdist`).
6. Using the system Python environment, with a simple setuptools
   invocation which will also work for plain distutils-based setup.py (just
   `sdist`).

The reason for this order is that it prefers first the setup.py invocations
which leave the fewest files on disk. Then, we prefer the Cloud SDK execution
environment as it will be the most stable.

package_root must be writable, or setuptools will fail (there are
temporary files from setuptools that get put in the CWD).

Args:
  package_root: str, the directory containing the package (that is, the
    *parent* of the package itself).
  setup_py_path: str, the path to the `setup.py` file to execute.
  output_dir: str, path to a directory in which the built packages should be
    created.

Returns:
  list of str, the full paths to the generated packages.

Raises:
  SysExecutableMissingError: if sys.executable is None
  RuntimeError: if the execution of setuptools exited non-zero.
sdistz
--dist-dirbuildz--build-basez--build-tempegg_infoz
--egg-baseNz!Python packaging resulted in [%s]r>   )rj   appendr   r   ioStringIOr   RuntimeErrorgetvaluerM   listdirrN   r?   r
   rS   )rq   r`   
output_dirworking_dir
sdist_args
build_argsegg_info_argssetup_py_arg_setssetup_py_commandsrp   setup_py_commandr   return_coderel_filelocal_pathss                  r   _RunSetupToolsr   7  sW   d % <4J
 	nkKJ  {;M"Z/ *<
 6 7:
 6 7 + .KKMc$,,S1k[	 . (( 	; &D "$J!79!7X j3!7  9))/;1GH	I &%B9s   BD0D0)E0
D>c           	      X   [         R                  R                  U 5      n [         R                  R                  U 5      n[	        U 5       n[        X#5      n[         R                  R                  [         R                  R                  U S5      5      (       d  [        U 5      e[         R                  R                  US5      n[         R                  R                  U 5      n[        XE5      n [        X$U5      U(       aA  [         R                  R                  US5      nXG4 H  n [         R                  " U5        M     sSSS5        $ ! [         a    [        R                  " SU5         ML  f = f! [          a%  n	[#        [$        R&                  " U	5      U5      eSn	A	ff = f! U(       aj  [         R                  R                  US5      nXG4 HB  n [         R                  " U5        M  ! [         a    [        R                  " SU5         M@  f = f   f f = f! , (       d  f       g= f)aJ  Builds Python packages from the given package source.

That is, builds Python packages from the code in package_path, using its
parent directory (the 'package root') as its context using the setuptools
`sdist` command.

If there is a `setup.py` file in the package root, use that. Otherwise,
use a simple, temporary one made for this package.

We try to be as unobstrustive as possible (see _RunSetupTools for details):

- setuptools writes some files to the package root--we move as many temporary
  generated files out of the package root as possible
- the final output gets written to output_dir
- any temporary setup.py file is written outside of the package root.
- if the current directory isn't writable, we silenly make a temporary copy

Args:
  package_path: str. Path to the package. This should be the path to
    the directory containing the Python code to be built, *not* its parent
    (which optionally contains setup.py and other metadata).
  output_dir: str, path to a long-lived directory in which the built packages
    should be created.

Returns:
  list of str. The full local path to all built Python packages.

Raises:
  SetuptoolsFailedError: If the setup.py file fails to successfully build.
  MissingInitError: If the package doesn't contain an `__init__.py` file.
  InvalidSourceDirError: if the source directory is not valid.
z__init__.pyzsetup.pyz	setup.pycz;Couldn't remove file [%s] (it may never have been created).N)rM   rN   abspathdirnamerj   rY   existsr?   r2   basenamerb   r   unlinkrU   r
   rS   r   r   six	text_type)
package_pathr   rq   r   r`   r[   r#   pyc_filerN   errs
             r   BuildPackagesr     s   B .,.,%%l@L77>>"'',,|]CDD \**GGLLz:M77##L1L(EILD 

 77<<k:"-DIIdO .3 &%8  IIM  A!#--"4i@@A 

 77<<k:"-DIIdO IIM . 
' &%s   
B#H.E39-H'E=H!E0	,H/E0	0H3
F"= FF""F%%.HG,*H,!HHHHH
H)c                    U(       d
  [        5       e[        R                  " [        [	        [
        R                  R                  U 5      5      5      n[        R                  " U5       VVs/ s H  u  p4US:  d  M  UPM     nnnU(       a  [        U5      eU  Vs/ s H#  of[
        R                  R                  U5      4PM%     nn[        R                  " XqR                  UR                  5      $ s  snnf s  snf )z;Uploads files after validating and transforming input type.   )rC   collectionsCounterlistr   rM   rN   r   r   	iteritemsr;   r   UploadFiles
bucket_refname)pathsstaging_locationcounterr   countr@   rN   upload_pairss           r   _UploadFilesByPathr     s    	
 
""S)9)95%A BC'(+g(>L(>%!)(>*L


++=BCUT))$/0U,C			\+F+F-22
4 4 M Ds   ,C2<C2*C8c           
      n   / n/ nU  HK  n[         R                  R                  U5      (       a  UR                  U5        M:  UR                  U5        MM     U(       a  [        R
                  R                  [        R
                  R                  U5      5      n[        U5       nUR                  [        U[        R
                  R                  US5      5      5        UR                  [        XB5      5        SSS5        U$ U(       a  UR                  [        XB5      5        U$ ! , (       d  f       U$ = f)a  Uploads Python packages (if necessary), building them as-specified.

An AI Platform job needs one or more Python packages to run. These Python
packages can be specified in one of three ways:

  1. As a path to a local, pre-built Python package file.
  2. As a path to a Cloud Storage-hosted, pre-built Python package file (paths
     beginning with 'gs://').
  3. As a local Python source tree (the `--package-path` flag).

In case 1, we upload the local files to Cloud Storage[1] and provide their
paths. These can then be given to the AI Platform API, which can fetch
these files.

In case 2, we don't need to do anything. We can just send these paths directly
to the AI Platform API.

In case 3, we perform a build using setuptools[2], and upload the resulting
artifacts to Cloud Storage[1]. The paths to these artifacts can be given to
the AI Platform API. See the `BuildPackages` method.

These methods of specifying Python packages may be combined.


[1] Uploads are to a specially-prefixed location in a user-provided Cloud
Storage staging bucket. If the user provides bucket `gs://my-bucket/`, a file
`package.tar.gz` is uploaded to
`gs://my-bucket/<job name>/<checksum>/package.tar.gz`.

[2] setuptools must be installed on the local user system.

Args:
  packages: list of str. Path to extra tar.gz packages to upload, if any. If
    empty, a package_path must be provided.
  package_path: str. Relative path to source directory to be built, if any. If
    omitted, one or more packages must be provided.
  staging_location: storage_util.ObjectReference. Cloud Storage prefix to
    which archives are uploaded. Not necessary if only remote packages are
    given.

Returns:
  list of str. Fully qualified Cloud Storage URLs (`gs://..`) from uploaded
    packages.

Raises:
  ValueError: If packages is empty, and building package_path produces no
    tar archives.
  SetuptoolsFailedError: If the setup.py file fails to successfully build.
  MissingInitError: If the package doesn't contain an `__init__.py` file.
  DuplicateEntriesError: If multiple files with the same name were provided.
  ArgumentError: if no packages were found in the given path or no
    staging_location was but uploads were required.
r"   N)r   ObjectReferenceIsStorageUrlr   rM   rN   r   r   rj   extendr   r?   r   )packagesr   r   remote_pathsr   packagerq   r   s           r   UploadPythonPackagesr     s    l ,+g##0099'"!	  77??277??<#@AL	,	';|')ww||K'JL M,[KL 
( 
  *;IJ	 
(	' 
s   $AD%%
D4c           	      N   SnU(       a!  [         R                  R                  X5      nU$ U(       an  [         R                  R                  UR                  SR                  UR                  R                  S5      S4 Vs/ s H  nU(       d  M  UPM     sn5      5      nU$ s  snf )zEGet the appropriate staging location for the job given the arguments.N/r   )r   r   FromBucketRefFromNamebucketr?   r   rstrip)job_idstaging_bucketjob_dirr   fs        r   GetStagingLocationr   /  s    #33AA  
	 #33<<gll.A.A#.F.8.: "@ .:=> #$ .: "@ AB 
"@s    B"B")r   NN)NNN)3r   
__future__r   r   r   r   r   
contextlibr   rM   rg   r.   googlecloudsdk.api_lib.storager   $googlecloudsdk.command_lib.ml_enginer   googlecloudsdk.corer   r	   r
   googlecloudsdk.core.utilr   r   	six.movesr   r^   Errorr   r   r+   r2   r8   r;   rC   rF   rY   rb   contextmanagerrj   with_metaclassABCMetaobjectrl   r   r   r   r   r   r   r   r   r   r   r   <module>r      s9   '  ' 
   	 	 
  7 8 * / # * 
  )) 
5. 5" 2 ") "(/ ($. $?/ ?N. N+\, ) )@(2c((f= (2VO &/ VrBJ4IX
r   