mammoth-rainbow-82717
11/07/2023, 1:36 PMresume functionality and the S3 client in conjunction, but having some difficulty.
It seems that to get the artefact from the original run (i.e., origin_run_id) I need to manually make an instance of the Run class for this class and then pass that in. This works, but then I have to know a-priori that I want the artefact for the original run and it wasn't subsequently made in a step of the resumed run. I guess I could use some try/except logic to first check if the artefact exists for the resumed run and, if not, then try the original run.
Just wondering what other people do when using these two bits of functionality together?flaky-plumber-70709
11/07/2023, 2:48 PMmammoth-rainbow-82717
11/07/2023, 2:54 PMimport json
import logging
import os
from metaflow import (
FlowSpec,
Run,
S3,
current,
environment,
project,
step,
workflow_specification,
)
from metaflow.plugins.datatools.s3.s3 import MetaflowS3NotFound
logger = logging.getLogger(__name__)
class S3FileFlow(FlowSpec):
"""
Metaflow pipeline that illustrates using Metaflow's S3 client for reading from & writing to S3.
See <https://docs.metaflow.org/scaling/data#data-in-s3-metaflows3> for more details.
"""
@step
def start(self):
"""Initialise the flow."""
data = '1,2,3'
with S3(run=self) as s3:
res = json.dumps({'data': data})
url = s3.put('data-test', res)
<http://logger.info|logger.info>('Key URL: %s', url)
self.next(self.read_from_batch)
@step
def read_from_batch(self):
"""Read the batch data from S3."""
try:
<http://logger.info|logger.info>('Trying to obtain key from current run.')
with S3(run=self) as s3:
data = s3.get('data-test').text
except MetaflowS3NotFound:
if current.origin_run_id:
<http://logger.info|logger.info>('Failed. Trying to obtain key from original run.')
run = Run(f'{current.flow_name}/{current.origin_run_id}')
with S3(run=run) as s3:
data = s3.get('data-test').text
else:
raise
<http://logger.info|logger.info>('File contents: %s', json.loads(data))
self.next(self.end)
@step
def end(self):
"""End the flow."""
print('Finished reading the data!')
if __name__ == '__main__':
S3FileFlow(mammoth-rainbow-82717
11/07/2023, 2:55 PMread_from_batch method.flaky-plumber-70709
11/07/2023, 2:57 PMrun=self is probably messing things up with the resumeflaky-plumber-70709
11/07/2023, 2:58 PMrun=self build up the s3 path using currentmammoth-rainbow-82717
11/07/2023, 2:59 PMrun=self in the call and replacing it with the prefix?flaky-plumber-70709
11/07/2023, 2:59 PMrun=self if the option building the path with current worksflaky-plumber-70709
11/07/2023, 3:00 PMmammoth-rainbow-82717
11/07/2023, 3:01 PMflaky-plumber-70709
11/07/2023, 3:08 PMimport json
import logging
import os
from metaflow import (
FlowSpec,
Run,
S3,
current,
environment,
project,
step,
workflow_specification,
)
from metaflow.plugins.datatools.s3.s3 import MetaflowS3NotFound
logger = logging.getLogger(__name__)
class S3FileFlow(FlowSpec):
"""
Metaflow pipeline that illustrates using Metaflow's S3 client for reading from & writing to S3.
See <https://docs.metaflow.org/scaling/data#data-in-s3-metaflows3> for more details.
"""
bucket = 'mybucket'
@step
def start(self):
"""Initialise the flow."""
data = '1,2,3'
self.path = f's3://{self.bucket}/metaflow-runs/{current.flow_name}/{current.run_id}'
with S3(s3root=self.path) as s3:
res = json.dumps({'data': data})
self.url = s3.put('data-test', res)
<http://logger.info|logger.info>('Key URL: %s', self.url)
self.next(self.read_from_batch)
@step
def read_from_batch(self):
"""Read the batch data from S3."""
try:
<http://logger.info|logger.info>('Trying to obtain key from current run.')
with S3(s3root=self.path) as s3:
data = s3.get('data-test').text
except MetaflowS3NotFound:
if current.origin_run_id:
<http://logger.info|logger.info>('Failed. Trying to obtain key from original run.')
run = Run(f'{current.flow_name}/{current.origin_run_id}')
with S3(run=run) as s3:
data = s3.get('data-test').text
else:
raise
<http://logger.info|logger.info>('File contents: %s', json.loads(data))
self.next(self.end)
@step
def end(self):
"""End the flow."""
print('Finished reading the data!')
if __name__ == '__main__':
S3FileFlow()flaky-plumber-70709
11/07/2023, 3:09 PMrun=self as a cause of this issuemammoth-rainbow-82717
11/07/2023, 3:13 PMflaky-plumber-70709
11/07/2023, 3:21 PMflaky-plumber-70709
11/07/2023, 3:22 PMflaky-plumber-70709
11/07/2023, 3:25 PMmammoth-rainbow-82717
11/07/2023, 3:34 PMself.path = ... type functionality, so it remains the same on the resume, right? I think it will work.
We have a lot of teams using Metaflow in our company now though, so would be good to understand the general expectation on the interaction between these two bits of functionality.
Ideally would like a generic solution, but I am guessing that doesn't currently exist?mammoth-rainbow-82717
11/07/2023, 3:36 PMflaky-plumber-70709
11/07/2023, 4:00 PMorigin_run_id associatedflaky-plumber-70709
11/07/2023, 4:01 PMrun=selfflaky-plumber-70709
11/07/2023, 4:02 PMflaky-plumber-70709
11/07/2023, 4:03 PMflow s3 path could trigger other services or other flowsflaky-plumber-70709
11/07/2023, 4:05 PMflaky-plumber-70709
11/07/2023, 4:06 PMmammoth-rainbow-82717
11/07/2023, 4:23 PMdef s3_resume_retry(f):
@wraps(f)
def wrapper(**kwargs):
try:
<http://logger.info|logger.info>('Using S3 client with current run.')
return f(**kwargs)
except MetaflowS3NotFound:
if current.origin_run_id:
<http://logger.info|logger.info>('Failed. Trying S3 client with original run.')
run = Run(f'{current.flow_name}/{current.origin_run_id}')
return f(**{**kwargs, **{'run': run}})
else:
raise
return wrapper
@s3_resume_retry
def download_data(*, run: Union[FlowSpec, "Run"], bucket: Optional[str] = None, prefix: Optional[str] = None,
s3root: Optional[str] = None, **kwargs):
with S3(run=run, bucket=bucket, prefix=prefix, s3root=s3root, **kwargs) as s3:
return s3.get('data-test').text
OK, so here is what I have as a more generic version than my first pipeline.
You have to move the data download into its own function so that you can decorate it, which is not ideal.mammoth-rainbow-82717
11/07/2023, 4:24 PMmammoth-rainbow-82717
11/07/2023, 4:24 PMresume for local debugging btw. Not a massive use case, I guess, but would be nice to have it work nicely with resume out of the box.flaky-plumber-70709
11/07/2023, 4:35 PMflaky-plumber-70709
11/07/2023, 4:36 PMmammoth-rainbow-82717
11/07/2023, 5:08 PMpath created from the current object - Sorry, what do you mean by this? You mean the example you shared?flaky-plumber-70709
11/07/2023, 5:12 PMbucket = 'mybucket' # <----- added a constant for no real good reason at all but maybe this would be something that would be meaningful as a param
@step
def start(self):
"""Initialise the flow."""
data = '1,2,3'
self.path = f's3://{self.bucket}/metaflow-runs/{current.flow_name}/{current.run_id}' # <------ path from current
with S3(s3root=self.path) as s3:
res = json.dumps({'data': data})
self.url = s3.put('data-test', res) # <-------- we're persisting the whole object path here
logger.info('Key URL: %s', self.url)
self.next(self.read_from_batch)flaky-plumber-70709
11/07/2023, 5:14 PMmammoth-rainbow-82717
11/07/2023, 5:53 PMrun argument. That's what I have seen internally and was also my natural inclination when using the tool.mammoth-rainbow-82717
11/07/2023, 5:57 PMresume.
Like I said, it's not a massive use case, so it is not a big deal either way.
If other have alternative solutions, would also know if anyone has faced/solved this issue in a different way before.