@@ -213,13 +213,15 @@ def create_job_resources(
213213 # if we know we are using a dependency pre-installed sdk container image.
214214 if not skip_prestaged_dependencies :
215215 requirements_cache_path = (
216- os .path .join (tempfile .gettempdir (), 'dataflow -requirements-cache' ) if
216+ os .path .join (tempfile .gettempdir (), 'beam -requirements-cache' ) if
217217 (setup_options .requirements_cache
218218 is None ) else setup_options .requirements_cache )
219219 if (setup_options .requirements_cache != SKIP_REQUIREMENTS_CACHE and
220220 not os .path .exists (requirements_cache_path )):
221221 os .makedirs (requirements_cache_path , exist_ok = True )
222222
223+ # Track packages to stage for this specific run.
224+ packages_to_stage = set ()
223225 # Stage a requirements file if present.
224226 if setup_options .requirements_file is not None :
225227 if not os .path .isfile (setup_options .requirements_file ):
@@ -245,12 +247,16 @@ def create_job_resources(
245247 'such as --requirements_file. ' )
246248
247249 if setup_options .requirements_cache != SKIP_REQUIREMENTS_CACHE :
248- (
250+ populate_cache_callable = (
249251 populate_requirements_cache if populate_requirements_cache else
250- Stager ._populate_requirements_cache )(
251- setup_options .requirements_file ,
252- requirements_cache_path ,
253- setup_options .requirements_cache_only_sources )
252+ Stager ._populate_requirements_cache )
253+
254+ downloaded_packages = populate_cache_callable (
255+ setup_options .requirements_file ,
256+ requirements_cache_path ,
257+ setup_options .requirements_cache_only_sources )
258+ if downloaded_packages :
259+ packages_to_stage .update (downloaded_packages )
254260
255261 if pypi_requirements :
256262 tf = tempfile .NamedTemporaryFile (mode = 'w' , delete = False )
@@ -260,18 +266,23 @@ def create_job_resources(
260266 # Populate cache with packages from PyPI requirements and stage
261267 # the files in the cache.
262268 if setup_options .requirements_cache != SKIP_REQUIREMENTS_CACHE :
263- (
269+ populate_cache_callable = (
264270 populate_requirements_cache if populate_requirements_cache else
265- Stager ._populate_requirements_cache )(
266- tf .name ,
267- requirements_cache_path ,
268- setup_options .requirements_cache_only_sources )
271+ Stager ._populate_requirements_cache )
272+ downloaded_packages = populate_cache_callable (
273+ tf .name ,
274+ requirements_cache_path ,
275+ setup_options .requirements_cache_only_sources )
276+ if downloaded_packages :
277+ packages_to_stage .update (downloaded_packages )
269278
270279 if (setup_options .requirements_cache != SKIP_REQUIREMENTS_CACHE ) and (
271280 setup_options .requirements_file is not None or pypi_requirements ):
272- for pkg in glob .glob (os .path .join (requirements_cache_path , '*' )):
273- resources .append (
274- Stager ._create_file_stage_to_artifact (pkg , os .path .basename (pkg )))
281+ for pkg in packages_to_stage :
282+ pkg_path = os .path .join (requirements_cache_path , pkg )
283+ if os .path .exists (pkg_path ):
284+ resources .append (
285+ Stager ._create_file_stage_to_artifact (pkg_path , pkg ))
275286
276287 # Handle a setup file if present.
277288 # We will build the setup package locally and then copy it to the staging
@@ -741,19 +752,26 @@ def _populate_requirements_cache(
741752 # the requirements file and will download package dependencies.
742753
743754 # The apache-beam dependency is excluded from requirements cache population
744- # because we stage the SDK separately.
755+ # because we stage the SDK separately.
745756 with tempfile .TemporaryDirectory () as temp_directory :
746757 tmp_requirements_filepath = Stager ._remove_dependency_from_requirements (
747758 requirements_file = requirements_file ,
748759 dependency_to_remove = 'apache-beam' ,
749760 temp_directory_path = temp_directory )
750761
762+ # Download to a temporary directory first, then copy to cache.
763+ # This allows us to track exactly which packages are needed for this
764+ # requirements file.
765+ download_dir = tempfile .mkdtemp (dir = temp_directory )
766+
751767 cmd_args = [
752768 Stager ._get_python_executable (),
753769 '-m' ,
754770 'pip' ,
755771 'download' ,
756772 '--dest' ,
773+ download_dir ,
774+ '--find-links' ,
757775 cache_dir ,
758776 '-r' ,
759777 tmp_requirements_filepath ,
@@ -781,6 +799,18 @@ def _populate_requirements_cache(
781799 _LOGGER .info ('Executing command: %s' , cmd_args )
782800 processes .check_output (cmd_args , stderr = processes .STDOUT )
783801
802+ # Get list of downloaded packages and copy them to the cache
803+ downloaded_packages = set ()
804+ for pkg_file in os .listdir (download_dir ):
805+ downloaded_packages .add (pkg_file )
806+ src_path = os .path .join (download_dir , pkg_file )
807+ dest_path = os .path .join (cache_dir , pkg_file )
808+ # Only copy if not already in cache
809+ if not os .path .exists (dest_path ):
810+ shutil .copy2 (src_path , dest_path )
811+
812+ return downloaded_packages
813+
784814 @staticmethod
785815 def _build_setup_package (
786816 setup_file : str ,
0 commit comments