From ef3e8cf013880198aff76c120429bee079ebe562 Mon Sep 17 00:00:00 2001 From: Francesco Bruni Date: Mon, 16 Dec 2024 15:51:18 +0100 Subject: [PATCH 1/3] feat: autogenerated by pre-commit --- docs/v3/api-ref/rest-api/server/schema.json | 21 ++++++++ src/prefect/client/schemas/filters.py | 5 ++ src/prefect/server/schemas/filters.py | 8 +++ tests/client/test_prefect_client.py | 55 +++++++++++++++++++++ ui-v2/src/api/prefect.ts | 5 ++ 5 files changed, 94 insertions(+) diff --git a/docs/v3/api-ref/rest-api/server/schema.json b/docs/v3/api-ref/rest-api/server/schema.json index 62986c65c961..d750ed1aaf23 100644 --- a/docs/v3/api-ref/rest-api/server/schema.json +++ b/docs/v3/api-ref/rest-api/server/schema.json @@ -15710,6 +15710,27 @@ ] ] }, + "any_": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Any", + "description": "A list of tags to include", + "examples": [ + [ + "tag-1", + "tag-2" + ] + ] + }, "is_null_": { "anyOf": [ { diff --git a/src/prefect/client/schemas/filters.py b/src/prefect/client/schemas/filters.py index 52bb7258e700..b2d506da65df 100644 --- a/src/prefect/client/schemas/filters.py +++ b/src/prefect/client/schemas/filters.py @@ -505,6 +505,11 @@ class DeploymentFilterTags(PrefectBaseModel, OperatorMixin): " superset of the list" ), ) + any_: Optional[List[str]] = Field( + default=None, + examples=[["tag-1", "tag-2"]], + description="A list of tags to include", + ) is_null_: Optional[bool] = Field( default=None, description="If true, only include deployments without tags" ) diff --git a/src/prefect/server/schemas/filters.py b/src/prefect/server/schemas/filters.py index f1633387e885..8863c826080d 100644 --- a/src/prefect/server/schemas/filters.py +++ b/src/prefect/server/schemas/filters.py @@ -1080,6 +1080,12 @@ class DeploymentFilterTags(PrefectOperatorFilterBaseModel): " superset of the list" ), ) + any_: Optional[List[str]] = Field( + default=None, + examples=[["tag-1", "tag-2"]], + description="A list of tags to include", + ) + is_null_: Optional[bool] = Field( default=None, description="If true, only include deployments without tags" ) @@ -1088,6 +1094,8 @@ def _get_filter_list(self) -> list[sa.ColumnElement[bool]]: filters: list[sa.ColumnElement[bool]] = [] if self.all_ is not None: filters.append(orm_models.Deployment.tags.has_all(_as_array(self.all_))) + if self.any_ is not None: + filters.append(orm_models.Deployment.tags.has_any(_as_array(self.any_))) if self.is_null_ is not None: filters.append( orm_models.Deployment.tags == [] diff --git a/tests/client/test_prefect_client.py b/tests/client/test_prefect_client.py index 4cf9b9550bb7..b41535d2a9a6 100644 --- a/tests/client/test_prefect_client.py +++ b/tests/client/test_prefect_client.py @@ -45,6 +45,8 @@ from prefect.client.schemas.filters import ( ArtifactFilter, ArtifactFilterKey, + DeploymentFilter, + DeploymentFilterTags, FlowFilter, FlowRunFilter, FlowRunFilterTags, @@ -785,6 +787,59 @@ def foo(): assert lookup.name == "test-deployment" +@pytest.mark.parametrize( + "deployment_tags,filter_tags,expected_match", + [ + # Basic single tag matching + (["tag-1"], ["tag-1"], True), + (["tag-2"], ["tag-1"], False), + # Any matching - should match if ANY tag in filter matches + (["tag-1", "tag-2"], ["tag-1", "tag-3"], True), + (["tag-1"], ["tag-1", "tag-2"], True), + (["tag-2"], ["tag-1", "tag-2"], True), + # No matches + (["tag-1"], ["tag-2", "tag-3"], False), + (["tag-1"], ["get-real"], False), + # Empty cases + ([], ["tag-1"], False), + (["tag-1"], [], False), + ], + ids=[ + "single_tag_match", + "single_tag_no_match", + "multiple_tags_partial_match", + "subset_match_1", + "subset_match_2", + "no_matching_tags", + "nonexistent_tag", + "empty_run_tags", + "empty_filter_tags", + ], +) +async def test_read_deployment_by_any_tag( + prefect_client, deployment_tags, filter_tags, expected_match +): + @flow + def moo_deng(): + pass + + flow_id = await prefect_client.create_flow(moo_deng) + + await prefect_client.create_deployment( + flow_id=flow_id, + name="moisturized-deployment", + tags=deployment_tags, + ) + deployment_responses = await prefect_client.read_deployments( + deployment_filter=DeploymentFilter(tags=DeploymentFilterTags(any_=filter_tags)) + ) + if expected_match: + assert len(deployment_responses) == 1 + assert deployment_responses[0].name == "moisturized-deployment" + else: + assert len(deployment_responses) == 0 + + async def test_read_deployment_by_name_fails_with_helpful_suggestion(prefect_client): """this is a regression test for https://github.com/PrefectHQ/prefect/issues/15571""" diff --git a/ui-v2/src/api/prefect.ts b/ui-v2/src/api/prefect.ts index bcbff9e8b841..44ac0fd719ae 100644 --- a/ui-v2/src/api/prefect.ts +++ b/ui-v2/src/api/prefect.ts @@ -5382,6 +5382,11 @@ export interface components { * @description A list of tags. Deployments will be returned only if their tags are a superset of the list */ all_?: string[] | null; + /** + * Any + * @description A list of tags to include + */ + any_?: string[] | null; /** * Is Null * @description If true, only include deployments without tags From f108a48e981a03a7482cdef7607c947f2374c5c4 Mon Sep 17 00:00:00 2001 From: nate nowack Date: Fri, 20 Dec 2024 08:53:38 -0600 Subject: [PATCH 2/3] `list` instead of `List` --- src/prefect/client/schemas/filters.py | 2 +- src/prefect/server/schemas/filters.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/prefect/client/schemas/filters.py b/src/prefect/client/schemas/filters.py index b2d506da65df..4df3ae049769 100644 --- a/src/prefect/client/schemas/filters.py +++ b/src/prefect/client/schemas/filters.py @@ -505,7 +505,7 @@ class DeploymentFilterTags(PrefectBaseModel, OperatorMixin): " superset of the list" ), ) - any_: Optional[List[str]] = Field( + any_: Optional[list[str]] = Field( default=None, examples=[["tag-1", "tag-2"]], description="A list of tags to include", diff --git a/src/prefect/server/schemas/filters.py b/src/prefect/server/schemas/filters.py index 6db4faee16bd..98cf6a047947 100644 --- a/src/prefect/server/schemas/filters.py +++ b/src/prefect/server/schemas/filters.py @@ -1158,7 +1158,7 @@ class DeploymentFilterTags(PrefectOperatorFilterBaseModel): " superset of the list" ), ) - any_: Optional[List[str]] = Field( + any_: Optional[list[str]] = Field( default=None, examples=[["tag-1", "tag-2"]], description="A list of tags to include", From 545d9a3a683233bbbe0acb107ac730dcb4678e6e Mon Sep 17 00:00:00 2001 From: zzstoatzz Date: Fri, 20 Dec 2024 12:18:15 -0600 Subject: [PATCH 3/3] add import --- src/prefect/server/schemas/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prefect/server/schemas/filters.py b/src/prefect/server/schemas/filters.py index 98cf6a047947..a8536066d248 100644 --- a/src/prefect/server/schemas/filters.py +++ b/src/prefect/server/schemas/filters.py @@ -11,7 +11,7 @@ from pydantic import ConfigDict, Field import prefect.server.schemas as schemas -from prefect.server.database import PrefectDBInterface, db_injector +from prefect.server.database import PrefectDBInterface, db_injector, orm_models from prefect.server.utilities.schemas.bases import PrefectBaseModel from prefect.types import DateTime from prefect.utilities.collections import AutoEnum