@@ -97,6 +97,7 @@ def __init__(
97
97
content_type = None ,
98
98
metadata = None ,
99
99
range_info = None ,
100
+ last_modified = None ,
100
101
):
101
102
102
103
# all fields of S3Object should return a unicode object
@@ -107,6 +108,7 @@ def __init__(
107
108
self ._path = path
108
109
self ._key = None
109
110
self ._content_type = content_type
111
+ self ._last_modified = last_modified
110
112
111
113
self ._metadata = None
112
114
if metadata is not None and "metaflow-user-attributes" in metadata :
@@ -237,6 +239,14 @@ def range_info(self):
237
239
"""
238
240
return self ._range_info
239
241
242
+ @property
243
+ def last_modified (self ):
244
+ """
245
+ Returns the last modified unix timestamp of the object, or None
246
+ if not fetched.
247
+ """
248
+ return self ._last_modified
249
+
240
250
def __str__ (self ):
241
251
if self ._path :
242
252
return "<S3Object %s (%d bytes, local)>" % (self ._url , self ._size )
@@ -486,6 +496,7 @@ def _info(s3, tmp):
486
496
"content_type" : resp ["ContentType" ],
487
497
"metadata" : resp ["Metadata" ],
488
498
"size" : resp ["ContentLength" ],
499
+ "last_modified" : resp ["LastModified" ].timestamp (),
489
500
}
490
501
491
502
info_results = None
@@ -504,6 +515,7 @@ def _info(s3, tmp):
504
515
size = info_results ["size" ],
505
516
content_type = info_results ["content_type" ],
506
517
metadata = info_results ["metadata" ],
518
+ last_modified = info_results ["last_modified" ],
507
519
)
508
520
return S3Object (self ._s3root , url , None )
509
521
@@ -547,7 +559,7 @@ def _head():
547
559
else :
548
560
yield self ._s3root , s3url , None , info ["size" ], info [
549
561
"content_type"
550
- ], info ["metadata" ]
562
+ ], info ["metadata" ], None , info [ "last_modified" ]
551
563
else :
552
564
# This should not happen; we should always get a response
553
565
# even if it contains an error inside it
@@ -593,6 +605,7 @@ def _download(s3, tmp):
593
605
return {
594
606
"content_type" : resp ["ContentType" ],
595
607
"metadata" : resp ["Metadata" ],
608
+ "last_modified" : resp ["LastModified" ].timestamp (),
596
609
}
597
610
return None
598
611
@@ -611,6 +624,7 @@ def _download(s3, tmp):
611
624
path ,
612
625
content_type = addl_info ["content_type" ],
613
626
metadata = addl_info ["metadata" ],
627
+ last_modified = addl_info ["last_modified" ],
614
628
)
615
629
return S3Object (self ._s3root , url , path )
616
630
@@ -652,7 +666,9 @@ def _get():
652
666
info = json .load (f )
653
667
yield self ._s3root , s3url , os .path .join (
654
668
self ._tmpdir , fname
655
- ), None , info ["content_type" ], info ["metadata" ]
669
+ ), None , info ["content_type" ], info ["metadata" ], None , info [
670
+ "last_modified"
671
+ ]
656
672
else :
657
673
yield self ._s3root , s3prefix , None
658
674
else :
@@ -694,7 +710,9 @@ def _get():
694
710
info = json .load (f )
695
711
yield self ._s3root , s3url , os .path .join (
696
712
self ._tmpdir , fname
697
- ), None , info ["content_type" ], info ["metadata" ]
713
+ ), None , info ["content_type" ], info ["metadata" ], None , info [
714
+ "last_modified"
715
+ ]
698
716
else :
699
717
yield s3prefix , s3url , os .path .join (self ._tmpdir , fname )
700
718
@@ -1023,6 +1041,7 @@ def _s3op_with_retries(self, mode, **options):
1023
1041
raise MetaflowS3NotFound (err_out )
1024
1042
elif ex .returncode == s3op .ERROR_URL_ACCESS_DENIED :
1025
1043
raise MetaflowS3AccessDenied (err_out )
1044
+ print ("Error with S3 operation:" , err_out )
1026
1045
time .sleep (2 ** i + random .randint (0 , 10 ))
1027
1046
1028
1047
return None , err_out
0 commit comments