<@ULVA73B9P> can I call a callback when a task run...
# ask-community
g
@Marvin can I call a callback when a task run is cancelled using the endpoint
api/task_runs/{id}/set_state
?
m
thought for 144.6 seconds
Based on my research, I need to clarify something: While you can use the API endpoint
api/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?
g
@Marvin can I call a callback when a task run is cancelled using the endpoint
api/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 endpoint
m
thought for 43.7 seconds
Based on my research, your current approach of triggering a cleanup flow after calling the set_state endpoint is actually the most reliable solution. Here's why: 1. No Built-in Hooks: There are no built-in hooks or callbacks that automatically trigger when a flow or task run is cancelled via the API endpoints. 2. API Endpoint Limitations: The
set_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:
Copy code
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?
g
@Marvin I have a flow run which has task runs A, B, C and D. D is started after allow_failure(C), C is started after B and B is started after A. If during the execution of A or B I cancel all task runs except D using the endpoint a`pi/task_runs/{id}/set_state`, would D be executed?
m
thought for 96.4 seconds
Based on my research, here's how this scenario would work: 1. If you cancel tasks A, B, and C using the API endpoint while A or B is running: - Task D should still execute because: a) It's downstream of
allow_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:
Copy code
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?
g
@Marvin would D still run even if C does not start because B has been cancelled?
m
thought for 64.1 seconds
Based on my research, I need to correct my previous answer: No, task D would not run if task C never starts due to B being cancelled. Here's why: 1.
allow_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:
Copy code
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:
Copy code
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?
g
@Marvin how can I read a block_document from the database?
m
thought for 49.3 seconds
Based on the research, there are two main ways to read a block document from the database in Prefect 3.x: 1. Using
Block.load()
(Recommended Method):
Copy code
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):
Copy code
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!
g
@Marvin I want to read a block directly from the postgres database, how can I do it? What query should I use it?
m
thought for 37.2 seconds
Based on my research, I should advise against directly querying the Postgres database for block documents. Here's why: 1. Security Concerns: Direct database access bypasses Prefect's security layers and validation mechanisms 2. Schema Changes: The database schema can change between versions, making direct queries brittle 3. Data Integrity: Prefect maintains relationships between different tables that could be corrupted by direct manipulation Instead, here are the recommended approaches to work with block documents: 1. Using the Prefect API (Recommended):
Copy code
python
from prefect.blocks.core import Block

# Load a block by name
block = Block.load("your-block-name")
2. Using PrefectClient (For Advanced Use Cases):
Copy code
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.
g
@Marvin in block_document table I have these columns id ,created,updated,name,data, block_schema_id, block_type_id , is_anonymous, block_type_name where the actual document content is stored?
m
thought for 39.1 seconds
Based on the research, here's what you need to know about the
data
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:
Copy code
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:
Copy code
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?
g
@Marvin why there are some block documents in block_document table which are anonymous ?
m
thought for 53.7 seconds
Based on the research and GitHub issues, I can explain why anonymous block documents exist and their purpose: 1. Automatic Creation: Anonymous blocks are typically created automatically by Prefect when: - Temporary storage is needed for internal operations - During deployment packaging and serialization - When blocks are created programmatically without explicitly specifying a name 2. Naming Convention: Anonymous blocks have names that follow the pattern: - Format:
anonymous:<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?
g
@Marvin In Prefect 3.2.14, the table block_document is getting bigger. I have less that 200 blocks in Prefect UI which are small json blocks. Looking closer I’ve seen thousands of anonymous-* blocks. Given that my blocks can be safely deleted, can I truncate block_document table without causing any Prefect damage? The concern I have is that Prefect itself is storing something (like data about Prefect migration database when I upgraded it), that is why I’m asking.
m
thought for 58.8 seconds
Based on the research and your specific situation, here's my recommendation: Instead of truncating the entire table, which could be risky, here's a safer approach to clean up the block documents: 1. Safer SQL Cleanup Approach:
Copy code
sql
-- 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:
Copy code
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?
g
@Marvin why are in Prefect anonymous blocks in block_document table? I always provide a block name
m
thought for 50.1 seconds
Based on the research and looking at GitHub issues, I can explain why anonymous blocks are being created even when you always provide names for your blocks. There are several internal Prefect processes that create anonymous blocks: 1. Deployment Packaging: - When you create a deployment, Prefect needs to store flow code and configuration - This process often creates temporary anonymous blocks to store serialized flow data - These blocks are used internally by Prefect to manage deployment state 2. Flow Execution State: - During flow runs, Prefect may create anonymous blocks to store temporary state information - These blocks can contain runtime configurations or intermediate results 3. Result Storage: - When tasks or flows produce results that need temporary storage - Especially when using certain storage blocks or result handlers 4. Internal System Operations: - Database migrations might create temporary blocks - System configuration storage - Caching operations The reason you're seeing thousands of these despite having only ~200 named blocks is likely due to: 1. Each deployment creation potentially creating multiple anonymous blocks 2. Each flow run potentially creating temporary storage blocks 3. Accumulation over time as these blocks aren't automatically cleaned up To manage this, you could: 1. Monitor Block Creation:
Copy code
sql
-- 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:
Copy code
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:
Copy code
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?