Giacomo Chiarella
07/15/2025, 8:49 AMapi/task_runs/{id}/set_state?Marvin
07/15/2025, 8:49 AMMarvin
07/15/2025, 8:51 AMapi/task_runs/{id}/set_state to cancel a task run, there isn't a direct callback mechanism specifically for cancellation events through the API endpoint.
However, there are a few alternatives you might consider:
1. State Change Hooks: You can set up state change handlers in your flow code using the on_completion, on_failure, or on_crashed decorators. However, these don't specifically handle cancellation events.
2. Automation: You could set up an automation in Prefect that monitors for task state changes to "Cancelled" and triggers your desired callback logic.
3. Custom Monitoring: You could implement your own monitoring solution that watches for state changes via the Prefect API and triggers appropriate callbacks in your application.
If you need to respond to cancellation events specifically, could you tell me more about your use case? This would help me suggest the most appropriate solution for your needs. For example:
- Are you trying to clean up resources when a task is cancelled?
- Do you need real-time notification of cancellation?
- Are you working with the task runs programmatically or through the UI?Giacomo Chiarella
07/15/2025, 8:55 AMapi/task_runs/{id}/set_state? I need to clean some resources programmatically when a task run is cancelled through the endpoint. I need to do the same when a flow run is cancelled through the endpoint api/flow_runs/{id}/set_state . The only way I found is to trigger clean up dag after I called the set_state endpointMarvin
07/15/2025, 8:55 AMMarvin
07/15/2025, 8:56 AMset_state endpoints are primarily designed for state management, not for triggering side effects or callbacks.
3. Best Practices for Your Scenario:
- Your approach of triggering a cleanup flow is a good pattern because:
- It's explicit and traceable
- It ensures cleanup happens even if the cancellation is triggered via API
- It keeps the cleanup logic separate and reusable
Here's a suggested pattern to formalize your approach:
python
from prefect import flow
from prefect.client import get_client
async def cancel_and_cleanup(flow_run_id: str):
async with get_client() as client:
# First, cancel the flow run
await client.set_flow_run_state(
flow_run_id=flow_run_id,
state={"type": "CANCELLED"}
)
# Then trigger your cleanup flow
await cleanup_flow(flow_run_id=flow_run_id)
@flow
async def cleanup_flow(flow_run_id: str):
# Your cleanup logic here
...
Alternative Approaches You Could Consider:
1. Structured Concurrency: If you're working with async code, you could use AnyIO's cancellation scopes within your flows to handle cleanup when possible. However, this won't help with API-triggered cancellations.
2. State Change Automations: You could create a Prefect automation that triggers your cleanup flow whenever a flow or task enters a "Cancelled" state. This would be more automatic but might introduce some latency.
3. Monitoring Service: You could implement a separate service that monitors flow/task states and triggers cleanup when needed. This would be more complex but could provide more control.
Given your specific need to handle API-triggered cancellations, your current approach of explicitly triggering cleanup after calling the set_state endpoint is actually a solid solution. It's clear, maintainable, and ensures that cleanup happens reliably.
Would you like me to elaborate on any of these approaches or provide more specific implementation details for your use case?Giacomo Chiarella
07/15/2025, 9:11 AMMarvin
07/15/2025, 9:11 AMMarvin
07/15/2025, 9:13 AMallow_failure(C), which means it's designed to run regardless of C's state
b) API-triggered cancellation of specific task runs doesn't automatically cancel downstream tasks
However, there are some important caveats:
1. Flow Run State: Make sure you're only cancelling the specific task runs and not the entire flow run. If you cancel the flow run, all tasks including D will be cancelled.
2. Timing Matters:
- If you cancel A while it's running, B won't start (as it depends on A)
- If you cancel B while it's running, C won't start
- But in either case, D should still execute after allow_failure(C)
Here's a code example to illustrate this behavior:
python
from prefect import flow, task, allow_failure
@task
async def task_a():
return "A"
@task
async def task_b():
return "B"
@task
async def task_c():
return "C"
@task
async def task_d():
return "D"
@flow
async def my_flow():
a = await task_a()
b = await task_b(wait_for=[a])
c = await task_c(wait_for=[b])
# D will run even if C is cancelled because of allow_failure
d = await task_d(wait_for=[allow_failure(c)])
To verify this behavior in your specific case, I recommend:
1. Running a test flow with this pattern
2. Using the API to cancel tasks A, B, and C during execution
3. Monitoring the state transitions in the UI or via the API
Would you like me to provide more details about any aspect of this behavior or help you set up a test case to verify it?Giacomo Chiarella
07/15/2025, 9:16 AMMarvin
07/15/2025, 9:16 AMMarvin
07/15/2025, 9:17 AMallow_failure(C) only handles cases where task C actually runs and fails or is cancelled during its execution.
2. If task C never starts (because B was cancelled), then the dependency chain is broken before allow_failure even comes into play.
The sequence would be:
1. Task B is cancelled
2. Because B is cancelled, C never starts (it remains in a "Pending" state)
3. Since C never starts, D won't run, even with allow_failure(C)
allow_failure is designed to handle failures and cancellations of tasks that actually start executing, not to bypass the entire dependency chain. If you need D to run regardless of whether C starts or not, you would need to restructure your flow to remove the dependency chain or implement a different pattern.
If you need D to run regardless of what happens to A, B, or C, you might want to consider one of these alternatives:
1. Make D completely independent:
python
@flow
async def my_flow():
a = await task_a()
b = await task_b(wait_for=[a])
c = await task_c(wait_for=[b])
# D runs independently
d = await task_d()
2. Use parallel execution if D should start at the same time as A:
python
@flow
async def my_flow():
# Start both paths simultaneously
results = await anyio.create_task_group(
task_a(), # This starts the A -> B -> C chain
task_d() # This runs independently
)
3. If you need some form of dependency but want D to run even if previous tasks don't start, you might need to implement custom state handling or use a different flow structure entirely.
Would you like me to elaborate on any of these alternatives or help you design a flow structure that better meets your needs?Giacomo Chiarella
08/03/2025, 7:28 AMMarvin
08/03/2025, 7:28 AMMarvin
08/03/2025, 7:29 AMBlock.load() (Recommended Method):
python
from prefect.blocks.core import Block
# Load a block document by name
my_block = Block.load("your-block-name")
2. Using PrefectClient (For more direct database access):
python
from prefect.client import get_client
async def get_block_document(block_document_id):
client = get_client()
block_document = await client.read_block_document(
block_document_id,
include_secrets=True # Set to False if you don't need secret values
)
return block_document
The first method using Block.load() is the recommended approach as it:
- Is simpler to use
- Handles type checking and validation
- Provides a more intuitive interface
- Works synchronously (no need for async/await)
The second method using PrefectClient is more low-level and might be useful when you:
- Need to access the raw block document data
- Want to work with block documents programmatically at a lower level
- Need to access specific versions or metadata about the block document
Remember that block documents are typically created and managed through the Prefect UI or API, and these methods are for reading existing block documents. The block name used in Block.load() should match the name you gave the block when you created it.
Let me know if you need any clarification or have questions about specific block types!Giacomo Chiarella
08/03/2025, 7:29 AMMarvin
08/03/2025, 7:29 AMMarvin
08/03/2025, 7:30 AMpython
from prefect.blocks.core import Block
# Load a block by name
block = Block.load("your-block-name")
2. Using PrefectClient (For Advanced Use Cases):
python
from prefect.client import get_client
async def get_block_document(block_document_id):
client = get_client()
block_document = await client.read_block_document(
block_document_id,
include_secrets=True
)
return block_document
However, if you absolutely must query the database directly (for debugging or maintenance purposes), the block documents are stored in the block_document table. The schema includes:
- `id`: The block document ID
- `name`: The name of the block
- `data`: The block's configuration data (JSON)
- `block_type_id`: References the block type
- `block_type_name`: The name of the block type (added in newer versions)
But I strongly recommend against direct database queries because:
1. The schema has changed over time (as evidenced by the GitHub issues I found)
2. The data structure is complex and includes relationships with other tables
3. You might miss important validation and security checks that Prefect provides
Is there a specific reason you need direct database access? I might be able to suggest a more appropriate solution for your use case.Giacomo Chiarella
08/03/2025, 7:32 AMMarvin
08/03/2025, 7:32 AMMarvin
08/03/2025, 7:32 AMdata column:
1. Content Structure: The data column contains a JSON object with two main sections:
- `block_type_slug`: Identifies the type of block
- `configuration`: Contains the actual block settings and values
2. Example Structure:
json
{
"block_type_slug": "slack-webhook",
"configuration": {
"url": "<https://hooks.slack.com/...>",
"timeout": 60
// ... other block-specific configuration
}
}
3. Important Notes:
- Sensitive information (like passwords or API keys) in the configuration is encrypted before being stored
- The structure of the configuration object varies depending on the block type
- The data column works in conjunction with block_type_id and block_schema_id to maintain data integrity
If you need to query this data directly (though again, not recommended), you can use Postgres JSON operators. For example:
sql
SELECT data->>'block_type_slug' as block_type,
data->'configuration' as config
FROM block_document
WHERE id = 'your-block-id';
However, it's strongly recommended to use Prefect's API methods to interact with blocks, as they handle:
- Proper deserialization of the data
- Decryption of sensitive values
- Type validation
- Schema validation
Is there something specific you're trying to find or modify in the block documents?Giacomo Chiarella
08/03/2025, 7:40 AMMarvin
08/03/2025, 7:40 AMMarvin
08/03/2025, 7:41 AManonymous:<hash> or anonymous-<uuid>
- Example: "anonymous:48388c349c907ec7959e911b9d42eebd"
3. Use Cases:
- Storing serialized flow data during deployment
- Temporary storage of configuration during runtime
- Internal system operations where persistent naming isn't necessary
- When blocks are used as part of a larger operation but don't need to be referenced later
4. Characteristics:
- They are typically temporary
- May be cleaned up automatically
- Don't require user management
- Cannot be easily referenced by name (and aren't meant to be)
The is_anonymous flag serves several purposes:
- Helps distinguish between user-created blocks and system-created blocks
- May affect how the block is treated during cleanup operations
- Indicates the block wasn't meant for direct user reference
- Helps internal systems identify temporary or system-managed blocks
If you're seeing anonymous blocks in your database, it's completely normal and part of Prefect's internal operations. You generally don't need to worry about them unless you're doing system maintenance or debugging. They're part of how Prefect manages internal state and temporary configurations.
Would you like to know more about a specific aspect of anonymous blocks or are you trying to solve a particular problem related to them?Giacomo Chiarella
08/04/2025, 12:45 PMMarvin
08/04/2025, 12:45 PMMarvin
08/04/2025, 12:46 PMsql
-- First, backup your existing named blocks (non-anonymous ones)
CREATE TEMP TABLE backup_blocks AS
SELECT * FROM block_document
WHERE is_anonymous = false;
-- Delete only anonymous blocks that are older than a certain period
-- Adjust the interval as needed (e.g., '7 days'::interval for weekly cleanup)
DELETE FROM block_document
WHERE is_anonymous = true
AND created < (NOW() - '30 days'::interval);
-- If something goes wrong, you can restore your named blocks
-- INSERT INTO block_document SELECT * FROM backup_blocks;
2. Important Considerations:
- Anonymous blocks are generally safe to delete as they're typically temporary
- The system will create new anonymous blocks as needed
- Your UI-visible blocks (non-anonymous) should be preserved
- Consider keeping more recent anonymous blocks (e.g., last 7-30 days) as a safety measure
3. Before Cleanup:
- Take a database backup
- Run the cleanup during a maintenance window
- Monitor Prefect operations after cleanup to ensure everything works as expected
4. Regular Maintenance:
You could set up a regular cleanup job using a Prefect flow:
python
from prefect import flow
from prefect.blocks.system import JSON
import sqlalchemy as sa
@flow
def cleanup_anonymous_blocks():
# Using SQLAlchemy for safety
engine = sa.create_engine('your_database_url')
with engine.begin() as conn:
conn.execute(sa.text("""
DELETE FROM block_document
WHERE is_anonymous = true
AND created < (NOW() - interval '30 days')
"""))
5. Monitor After Cleanup:
- Check that your existing flows and deployments work correctly
- Verify that your named blocks are still accessible
- Monitor the database size to confirm the cleanup was effective
The accumulation of anonymous blocks is a known behavior, and cleaning them up periodically is reasonable. However, I recommend:
1. Start with older anonymous blocks first (e.g., > 30 days old)
2. Monitor system behavior after cleanup
3. If everything works well, you can be more aggressive with the cleanup in the future
Would you like me to provide more specific SQL queries or help you set up a maintenance flow for this cleanup?Giacomo Chiarella
08/04/2025, 12:49 PMMarvin
08/04/2025, 12:49 PMMarvin
08/04/2025, 12:49 PMsql
-- See what's creating anonymous blocks
SELECT
substring(name from 1 for 50) as block_name,
created,
block_type_name,
is_anonymous
FROM block_document
WHERE is_anonymous = true
ORDER BY created DESC
LIMIT 10;
2. Implement Regular Cleanup:
sql
-- Safe cleanup of old anonymous blocks
DELETE FROM block_document
WHERE is_anonymous = true
AND created < (CURRENT_TIMESTAMP - interval '7 days')
AND name NOT LIKE 'prefect-system-%'; -- Protect any system-related blocks
3. Track Block Growth:
sql
-- Monitor block growth over time
SELECT
date_trunc('day', created) as day,
count(*) as total_blocks,
sum(case when is_anonymous then 1 else 0 end) as anonymous_blocks
FROM block_document
GROUP BY date_trunc('day', created)
ORDER BY day DESC;
The accumulation of anonymous blocks is a known behavior in Prefect, and while they serve important internal purposes, they can be safely cleaned up periodically as long as you:
1. Only delete older anonymous blocks (e.g., > 7 days old)
2. Preserve named blocks
3. Monitor system behavior after cleanup
Would you like me to provide more specific queries or help you analyze what's creating these anonymous blocks in your environment?