New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[task scheduling] avoid relative module path problem with subcription keys #12227
Conversation
✅ Deploy Preview for prefect-docs-preview ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
src/prefect/engine.py
Outdated
@@ -2966,6 +2966,15 @@ async def create_autonomous_task_run(task: Task, parameters: Dict[str, Any]) -> | |||
factory = await ResultFactory.from_autonomous_task(task, client=client) | |||
await factory.store_parameters(parameters_id, parameters) | |||
|
|||
if not task.task_origin_hash: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the only way we should get here is if we cannot get the source file of the function directly - I'm not actually sure in what real world case that would be (i can make it happen by passing something like math.sqrt
), but trying indiscriminately to get the source file we failed some of the test_tasks.py
cases when a task is instantiated from a callable object like
class A:
def __call__(self, *_args: Any, **_kwargs: Any) -> Any:
return "hello"
task_instance = Task(fn=A)
even though I seem to have no problem submitting runs of this task instance 🤔
should be an extreme edge case but just flagging in case anyone has thoughts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. @zzstoatzz Are you saying that the example of a callable object doesn't work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it actually does!
even though I seem to have no problem submitting runs of this task instance 🤔
but I'm saying without the try/except
in Task.__init__
which handles failures by setting this hash to None
, we fail some of the test_tasks.py
tests where we are testing things about tasks instantiated in this way
I tried to reproduce the failure I saw in the test by creating a task from this callable class A
and then submitting it, but it actually worked. So it wasn't the most satisfying resolution but wanted to flag in case someone had a better idea of what was happening
…com/PrefectHQ/prefect into task-scheduling-avoid-task-key-trap
…-scheduling-avoid-task-key-trap
src/prefect/engine.py
Outdated
" `TaskServer` instance is capable of running the task." | ||
) | ||
|
||
task.task_key = task.task_origin_hash |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, won't this obscure the task key for all functions? I thought we only wanted to do this for ones that fell into the __main__
trap?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm yeah it does, updated in 7507ba3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dig it!
With 2.16.4 the import is still required in the example below. task_server.py: from prefect import task
from prefect.task_server import serve
@task
def my_b_task(name: str):
print(f"Hello, {name}!")
return f"Hello, {name}!"
if __name__ == "__main__":
from task_server import my_b_task # this is required for the task to run
serve(my_b_task) task_submitter.py
However, with a FastAPI server it is not if you just hit the API endpoint first_fastapi.py from fastapi import FastAPI
from prefect import task
from ff_prefect_task_server import my_fastapi_task
app = FastAPI()
@task
def my_b_task(name: str):
print(f"Hello, {name}!")
return f"Hello, {name}!"
@app.get("/ptask")
async def prefect_task():
val = my_fastapi_task.submit(name="Marvin")
return {"message": f"Prefect Task submitted: {val}"} ff_prefect_task_server.py from prefect import task
from prefect.task_server import serve
@task
def my_fastapi_task(name: str):
print(f"Hello, {name}!")
if __name__ == "__main__":
serve(my_fastapi_task) |
hey @discdiver - thanks, I'll look at this today |
previously (as discussed here) we relied on the
task_key
as set byTask.__init__
to match task runs onTaskRun
s to those aTaskServer
was given at start-up, but this had a sneaky problemsay you setup your tasks and serve them:
Here:
task_key == '__main__.thing'
but if you import and then submit that task somewhere else:
you end up with
thing.task_key == 'myapp.tasks.thing'
so the
TaskServer
would never pick these upThis PR makes the subscription keys a hash of the task's name and the absolute path of where the task was defined, to ensure we don't have key mismatches related to relative module paths, without requiring a somewhat unintuitive thing like
from . import tasks