We can use two things here:
- An incremental "last updated" output
- Abort transaction
We'll store the state of when we last ran inside a second dataset output, and if we detect the timestamp we stored there is less than a certain threshold away, we will abort our run.
This code will look like the following:
from transforms.api import Input, Output, transform, incremental
from pyspark.sql import types as T
from datetime import datetime, timedelta
MIN_TIME_ELAPSED = timedelta(minutes=30)
RUN_RECORD_SCHEMA = StructType([ \
StructField("last_run", T.TimestampType(), False),
])
def maybe_get_run_rows(run_record):
last_run_df = run_record.dataframe("previous", schema=RUN_RECORD_SCHEMA)
last_run_rows = last_run_df.collect()
return last_run_rows
def within_run_window(run_record_rows):
current_run_time = datetime.now()
last_run_time = run_record_rows[0][0]
return (current_run_time - last_run_time) > MIN_TIME_ELAPSED:
def record_run(run_record, ctx):
# Record that we ran
new_run_record_df = ctx.spark_session.createDataFrame(
[[current_run_time]],
schema=RUN_RECORD_SCHEMA
)
run_record.set_mode("replace")
run_record.write_dataframe(new_run_record_df)
@incremental(semantic_version=1)
@transform(
my_output=Output("/my/output/dataset"),
run_record=Output("/my/output/run_record"),
my_input=Input("/my/input/dataset")
)
def my_compute_function(my_input, run_record, my_output, ctx):
input_df = my_input.dataframe()
run_record_rows = maybe_get_run_rows(run_record)
if len(run_record_rows) == 0:
# First run ever
# Do some rate-limited processing...
my_call()
record_run(run_record, ctx)
else:
# Not first run ever
if not within_run_window(run_record_rows):
# Built too quickly, do nothing
my_output.abort()
return
else:
# Proceed as normal
my_call()
record_run(run_record, ctx)
You can now configure the MIN_TIME_ELAPSED argument by passing the minimum time elapsed amount you'd like, per the timedelta docs
If for some reason you wanted to only run one time ever, then you could slightly modify your compute function to instead be:
@incremental(semantic_version=1)
@transform(
my_output=Output("/my/output/dataset"),
run_record=Output("/my/output/run_record"),
my_input=Input("/my/input/dataset")
)
def my_compute_function(my_input, run_record, my_output, ctx):
input_df = my_input.dataframe()
run_record_rows = maybe_get_run_rows(run_record)
if len(run_record_rows) == 0:
# First run ever
# Do some rate-limited processing...
my_call()
record_run(run_record, ctx)
else:
# Not first run ever
my_output.abort()
return