From 911640b8d5418251c0a7b5ec5c9847ae520a9c42 Mon Sep 17 00:00:00 2001 From: Anuragp22 Date: Fri, 29 May 2026 15:37:12 +0000 Subject: [PATCH 1/2] Type asset_expression in REST API responses instead of an untyped dict --- .../newsfragments/67692.improvement.rst | 1 + .../api_fastapi/core_api/datamodels/common.py | 88 +++++ .../api_fastapi/core_api/datamodels/dags.py | 3 +- .../core_api/datamodels/ui/assets.py | 43 +++ .../core_api/datamodels/ui/dags.py | 3 +- .../datamodels/ui/partitioned_dag_runs.py | 5 +- .../core_api/openapi/_private_ui.yaml | 198 ++++++++++- .../openapi/v2-rest-api-generated.yaml | 125 ++++++- .../api_fastapi/core_api/routes/ui/assets.py | 8 +- .../ui/openapi-gen/queries/ensureQueryData.ts | 2 +- .../ui/openapi-gen/queries/prefetch.ts | 2 +- .../airflow/ui/openapi-gen/queries/queries.ts | 2 +- .../ui/openapi-gen/queries/suspense.ts | 2 +- .../ui/openapi-gen/requests/schemas.gen.ts | 321 +++++++++++++++++- .../ui/openapi-gen/requests/services.gen.ts | 6 +- .../ui/openapi-gen/requests/types.gen.ts | 103 +++++- .../AssetExpression/AssetExpression.tsx | 6 +- .../src/components/AssetExpression/types.ts | 40 +-- .../ui/src/components/AssetProgressCell.tsx | 4 +- .../ui/src/pages/DagsList/AssetSchedule.tsx | 6 +- .../core_api/datamodels/test_common.py | 77 +++++ .../airflowctl/api/datamodels/generated.py | 234 +++++++++---- 22 files changed, 1128 insertions(+), 151 deletions(-) create mode 100644 airflow-core/newsfragments/67692.improvement.rst create mode 100644 airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/assets.py create mode 100644 airflow-core/tests/unit/api_fastapi/core_api/datamodels/test_common.py diff --git a/airflow-core/newsfragments/67692.improvement.rst b/airflow-core/newsfragments/67692.improvement.rst new file mode 100644 index 0000000000000..8de5c2878d6b3 --- /dev/null +++ b/airflow-core/newsfragments/67692.improvement.rst @@ -0,0 +1 @@ +Type the ``asset_expression`` field in DAG REST API responses with a structured scheduling-expression model instead of an untyped object, so generated API clients describe its real shape rather than treating it as an opaque dictionary. diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/common.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/common.py index 48b55f2f1e684..99c85e651132b 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/common.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/common.py @@ -29,6 +29,94 @@ from airflow.api_fastapi.core_api.base import BaseModel, StrictBaseModel +# Asset Scheduling Expression Data Models +# +# These mirror the JSON produced by ``BaseAsset.as_expression()`` (see +# ``airflow.serialization.definitions.assets``), which is stored verbatim in +# ``DagModel.asset_expression``. Declaring them gives the REST API -- and the +# TypeScript client generated from its OpenAPI spec -- a real type instead of an +# opaque ``dict``. The shape is a recursive boolean tree whose leaves are assets, +# asset aliases, or asset references. + + +class AssetExpressionAssetInfo(BaseModel): + """ + Body of an ``asset`` leaf node. + + ``id`` is injected by ``DagModelOperation.update_dag_asset_expression`` when the expression is + persisted; ``BaseAsset.as_expression()`` itself only emits ``uri``/``name``/``group``. It is left + optional so a row persisted before id-enrichment (or migrated from the pre-3.0 dataset format) + degrades gracefully instead of failing response validation. + """ + + uri: str + name: str + group: str + id: int | None = None + + +class AssetExpressionAliasInfo(BaseModel): + """Body of an ``alias`` leaf node.""" + + name: str + group: str + + +class AssetExpressionAsset(BaseModel): + """An asset leaf: ``{"asset": {"uri": ..., "name": ..., "group": ...}}``.""" + + asset: AssetExpressionAssetInfo + + +class AssetExpressionAlias(BaseModel): + """An asset alias leaf: ``{"alias": {"name": ..., "group": ...}}``.""" + + alias: AssetExpressionAliasInfo + + +class AssetExpressionRef(BaseModel): + """An unresolved asset reference leaf: ``{"asset_ref": {"name": ...}}`` or ``{"asset_ref": {"uri": ...}}``.""" + + asset_ref: dict[str, str] + + +class AssetExpressionAny(BaseModel): + """An "or" node: ``{"any": [...]}`` -- satisfied when any child is satisfied.""" + + any: list[AssetExpression] + + +class AssetExpressionAll(BaseModel): + """An "and" node: ``{"all": [...]}`` -- satisfied when all children are satisfied.""" + + all: list[AssetExpression] + + +def _asset_expression_discriminator(value: Any) -> str | None: + """Select an expression variant by the single key that is present.""" + keys = ("asset", "alias", "asset_ref", "any", "all") + if isinstance(value, dict): + present = [key for key in keys if key in value] + else: + present = [key for key in keys if getattr(value, key, None) is not None] + return present[0] if len(present) == 1 else None + + +AssetExpression = Annotated[ + Union[ + Annotated[AssetExpressionAsset, Tag("asset")], + Annotated[AssetExpressionAlias, Tag("alias")], + Annotated[AssetExpressionRef, Tag("asset_ref")], + Annotated[AssetExpressionAny, Tag("any")], + Annotated[AssetExpressionAll, Tag("all")], + ], + Discriminator(_asset_expression_discriminator), +] +"""A nested asset scheduling expression; see ``BaseAsset.as_expression()``.""" + +AssetExpressionAny.model_rebuild() +AssetExpressionAll.model_rebuild() + # Common Bulk Data Models T = TypeVar("T") K = TypeVar("K") diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dags.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dags.py index dea97c4635281..09a854d75e8b3 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dags.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dags.py @@ -35,6 +35,7 @@ from airflow._shared.module_loading import qualname from airflow.api_fastapi.core_api.base import BaseModel, StrictBaseModel, make_partial_model +from airflow.api_fastapi.core_api.datamodels.common import AssetExpression from airflow.api_fastapi.core_api.datamodels.dag_tags import DagTagResponse from airflow.api_fastapi.core_api.datamodels.dag_versions import DagVersionResponse from airflow.configuration import conf @@ -191,7 +192,7 @@ class DAGDetailsResponse(DAGResponse): catchup: bool dag_run_timeout: timedelta | None - asset_expression: dict | None + asset_expression: AssetExpression | None doc_md: str | None start_date: datetime | None end_date: datetime | None diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/assets.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/assets.py new file mode 100644 index 0000000000000..25efc9f5079ee --- /dev/null +++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/assets.py @@ -0,0 +1,43 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +from datetime import datetime + +from pydantic import Field + +from airflow.api_fastapi.core_api.base import BaseModel +from airflow.api_fastapi.core_api.datamodels.common import AssetExpression + + +class NextRunAssetEventResponse(BaseModel): + """An asset, and the time of its latest event, that a DAG's next run is waiting on.""" + + id: int + uri: str + name: str + # Serialized as ``lastUpdate`` for the UI; ``None`` until the asset has a qualifying event. + last_update: datetime | None = Field(default=None, alias="lastUpdate") + + +class NextRunAssetsResponse(BaseModel): + """Assets feeding a DAG's next run, with the scheduling expression that combines them.""" + + asset_expression: AssetExpression | None = None + events: list[NextRunAssetEventResponse] + pending_partition_count: int | None = None diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dags.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dags.py index fc2fe3ed2d2aa..e245cb8c1082e 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dags.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dags.py @@ -18,6 +18,7 @@ from __future__ import annotations from airflow.api_fastapi.core_api.base import BaseModel +from airflow.api_fastapi.core_api.datamodels.common import AssetExpression from airflow.api_fastapi.core_api.datamodels.dags import DAGResponse from airflow.api_fastapi.core_api.datamodels.hitl import HITLDetail from airflow.api_fastapi.core_api.datamodels.ui.dag_runs import DAGRunLightResponse @@ -26,7 +27,7 @@ class DAGWithLatestDagRunsResponse(DAGResponse): """DAG with latest dag runs response serializer.""" - asset_expression: dict | None + asset_expression: AssetExpression | None latest_dag_runs: list[DAGRunLightResponse] pending_actions: list[HITLDetail] is_favorite: bool diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/partitioned_dag_runs.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/partitioned_dag_runs.py index 628f8560e96af..0fbdc095d6f68 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/partitioned_dag_runs.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/partitioned_dag_runs.py @@ -17,6 +17,7 @@ from __future__ import annotations from airflow.api_fastapi.core_api.base import BaseModel +from airflow.api_fastapi.core_api.datamodels.common import AssetExpression class PartitionedDagRunResponse(BaseModel): @@ -37,7 +38,7 @@ class PartitionedDagRunCollectionResponse(BaseModel): partitioned_dag_runs: list[PartitionedDagRunResponse] total: int - asset_expressions: dict[str, dict | None] | None = None + asset_expressions: dict[str, AssetExpression | None] | None = None class PartitionedDagRunAssetResponse(BaseModel): @@ -61,4 +62,4 @@ class PartitionedDagRunDetailResponse(BaseModel): assets: list[PartitionedDagRunAssetResponse] total_required: int total_received: int - asset_expression: dict | None = None + asset_expression: AssetExpression | None = None diff --git a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml index e6fb850247d91..c830e8cfc29c9 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml +++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml @@ -91,9 +91,7 @@ paths: content: application/json: schema: - type: object - additionalProperties: true - title: Response Next Run Assets + $ref: '#/components/schemas/NextRunAssetsResponse' '422': description: Validation Error content: @@ -1732,6 +1730,123 @@ paths: $ref: '#/components/schemas/HTTPValidationError' components: schemas: + AssetExpressionAlias: + properties: + alias: + $ref: '#/components/schemas/AssetExpressionAliasInfo' + type: object + required: + - alias + title: AssetExpressionAlias + description: 'An asset alias leaf: ``{"alias": {"name": ..., "group": ...}}``.' + AssetExpressionAliasInfo: + properties: + name: + type: string + title: Name + group: + type: string + title: Group + type: object + required: + - name + - group + title: AssetExpressionAliasInfo + description: Body of an ``alias`` leaf node. + AssetExpressionAll: + properties: + all: + items: + oneOf: + - $ref: '#/components/schemas/AssetExpressionAsset' + - $ref: '#/components/schemas/AssetExpressionAlias' + - $ref: '#/components/schemas/AssetExpressionRef' + - $ref: '#/components/schemas/AssetExpressionAny' + - $ref: '#/components/schemas/AssetExpressionAll' + type: array + title: All + type: object + required: + - all + title: AssetExpressionAll + description: 'An "and" node: ``{"all": [...]}`` -- satisfied when all children + are satisfied.' + AssetExpressionAny: + properties: + any: + items: + oneOf: + - $ref: '#/components/schemas/AssetExpressionAsset' + - $ref: '#/components/schemas/AssetExpressionAlias' + - $ref: '#/components/schemas/AssetExpressionRef' + - $ref: '#/components/schemas/AssetExpressionAny' + - $ref: '#/components/schemas/AssetExpressionAll' + type: array + title: Any + type: object + required: + - any + title: AssetExpressionAny + description: 'An "or" node: ``{"any": [...]}`` -- satisfied when any child is + satisfied.' + AssetExpressionAsset: + properties: + asset: + $ref: '#/components/schemas/AssetExpressionAssetInfo' + type: object + required: + - asset + title: AssetExpressionAsset + description: 'An asset leaf: ``{"asset": {"uri": ..., "name": ..., "group": + ...}}``.' + AssetExpressionAssetInfo: + properties: + uri: + type: string + title: Uri + name: + type: string + title: Name + group: + type: string + title: Group + id: + anyOf: + - type: integer + - type: 'null' + title: Id + type: object + required: + - uri + - name + - group + title: AssetExpressionAssetInfo + description: 'Body of an ``asset`` leaf node. + + + ``id`` is injected by ``DagModelOperation.update_dag_asset_expression`` when + the expression is + + persisted; ``BaseAsset.as_expression()`` itself only emits ``uri``/``name``/``group``. + It is left + + optional so a row persisted before id-enrichment (or migrated from the pre-3.0 + dataset format) + + degrades gracefully instead of failing response validation.' + AssetExpressionRef: + properties: + asset_ref: + additionalProperties: + type: string + type: object + title: Asset Ref + type: object + required: + - asset_ref + title: AssetExpressionRef + description: 'An unresolved asset reference leaf: ``{"asset_ref": {"name": ...}}`` + or ``{"asset_ref": {"uri": ...}}``.' AuthenticatedMeResponse: properties: id: @@ -2286,8 +2401,12 @@ components: title: Owners asset_expression: anyOf: - - additionalProperties: true - type: object + - oneOf: + - $ref: '#/components/schemas/AssetExpressionAsset' + - $ref: '#/components/schemas/AssetExpressionAlias' + - $ref: '#/components/schemas/AssetExpressionRef' + - $ref: '#/components/schemas/AssetExpressionAny' + - $ref: '#/components/schemas/AssetExpressionAll' - type: 'null' title: Asset Expression latest_dag_runs: @@ -3165,6 +3284,59 @@ components: - extra_menu_items title: MenuItemCollectionResponse description: Menu Item Collection serializer for responses. + NextRunAssetEventResponse: + properties: + id: + type: integer + title: Id + uri: + type: string + title: Uri + name: + type: string + title: Name + lastUpdate: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Lastupdate + type: object + required: + - id + - uri + - name + title: NextRunAssetEventResponse + description: An asset, and the time of its latest event, that a DAG's next run + is waiting on. + NextRunAssetsResponse: + properties: + asset_expression: + anyOf: + - oneOf: + - $ref: '#/components/schemas/AssetExpressionAsset' + - $ref: '#/components/schemas/AssetExpressionAlias' + - $ref: '#/components/schemas/AssetExpressionRef' + - $ref: '#/components/schemas/AssetExpressionAny' + - $ref: '#/components/schemas/AssetExpressionAll' + - type: 'null' + title: Asset Expression + events: + items: + $ref: '#/components/schemas/NextRunAssetEventResponse' + type: array + title: Events + pending_partition_count: + anyOf: + - type: integer + - type: 'null' + title: Pending Partition Count + type: object + required: + - events + title: NextRunAssetsResponse + description: Assets feeding a DAG's next run, with the scheduling expression + that combines them. NodeResponse: properties: id: @@ -3270,8 +3442,12 @@ components: anyOf: - additionalProperties: anyOf: - - additionalProperties: true - type: object + - oneOf: + - $ref: '#/components/schemas/AssetExpressionAsset' + - $ref: '#/components/schemas/AssetExpressionAlias' + - $ref: '#/components/schemas/AssetExpressionRef' + - $ref: '#/components/schemas/AssetExpressionAny' + - $ref: '#/components/schemas/AssetExpressionAll' - type: 'null' type: object - type: 'null' @@ -3321,8 +3497,12 @@ components: title: Total Received asset_expression: anyOf: - - additionalProperties: true - type: object + - oneOf: + - $ref: '#/components/schemas/AssetExpressionAsset' + - $ref: '#/components/schemas/AssetExpressionAlias' + - $ref: '#/components/schemas/AssetExpressionRef' + - $ref: '#/components/schemas/AssetExpressionAny' + - $ref: '#/components/schemas/AssetExpressionAll' - type: 'null' title: Asset Expression type: object diff --git a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml index df21d7570c529..202e9bf4355c0 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml +++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml @@ -11284,6 +11284,123 @@ components: - timestamp title: AssetEventResponse description: Asset event serializer for responses. + AssetExpressionAlias: + properties: + alias: + $ref: '#/components/schemas/AssetExpressionAliasInfo' + type: object + required: + - alias + title: AssetExpressionAlias + description: 'An asset alias leaf: ``{"alias": {"name": ..., "group": ...}}``.' + AssetExpressionAliasInfo: + properties: + name: + type: string + title: Name + group: + type: string + title: Group + type: object + required: + - name + - group + title: AssetExpressionAliasInfo + description: Body of an ``alias`` leaf node. + AssetExpressionAll: + properties: + all: + items: + oneOf: + - $ref: '#/components/schemas/AssetExpressionAsset' + - $ref: '#/components/schemas/AssetExpressionAlias' + - $ref: '#/components/schemas/AssetExpressionRef' + - $ref: '#/components/schemas/AssetExpressionAny' + - $ref: '#/components/schemas/AssetExpressionAll' + type: array + title: All + type: object + required: + - all + title: AssetExpressionAll + description: 'An "and" node: ``{"all": [...]}`` -- satisfied when all children + are satisfied.' + AssetExpressionAny: + properties: + any: + items: + oneOf: + - $ref: '#/components/schemas/AssetExpressionAsset' + - $ref: '#/components/schemas/AssetExpressionAlias' + - $ref: '#/components/schemas/AssetExpressionRef' + - $ref: '#/components/schemas/AssetExpressionAny' + - $ref: '#/components/schemas/AssetExpressionAll' + type: array + title: Any + type: object + required: + - any + title: AssetExpressionAny + description: 'An "or" node: ``{"any": [...]}`` -- satisfied when any child is + satisfied.' + AssetExpressionAsset: + properties: + asset: + $ref: '#/components/schemas/AssetExpressionAssetInfo' + type: object + required: + - asset + title: AssetExpressionAsset + description: 'An asset leaf: ``{"asset": {"uri": ..., "name": ..., "group": + ...}}``.' + AssetExpressionAssetInfo: + properties: + uri: + type: string + title: Uri + name: + type: string + title: Name + group: + type: string + title: Group + id: + anyOf: + - type: integer + - type: 'null' + title: Id + type: object + required: + - uri + - name + - group + title: AssetExpressionAssetInfo + description: 'Body of an ``asset`` leaf node. + + + ``id`` is injected by ``DagModelOperation.update_dag_asset_expression`` when + the expression is + + persisted; ``BaseAsset.as_expression()`` itself only emits ``uri``/``name``/``group``. + It is left + + optional so a row persisted before id-enrichment (or migrated from the pre-3.0 + dataset format) + + degrades gracefully instead of failing response validation.' + AssetExpressionRef: + properties: + asset_ref: + additionalProperties: + type: string + type: object + title: Asset Ref + type: object + required: + - asset_ref + title: AssetExpressionRef + description: 'An unresolved asset reference leaf: ``{"asset_ref": {"name": ...}}`` + or ``{"asset_ref": {"uri": ...}}``.' AssetResponse: properties: id: @@ -12657,8 +12774,12 @@ components: title: Dag Run Timeout asset_expression: anyOf: - - additionalProperties: true - type: object + - oneOf: + - $ref: '#/components/schemas/AssetExpressionAsset' + - $ref: '#/components/schemas/AssetExpressionAlias' + - $ref: '#/components/schemas/AssetExpressionRef' + - $ref: '#/components/schemas/AssetExpressionAny' + - $ref: '#/components/schemas/AssetExpressionAll' - type: 'null' title: Asset Expression doc_md: diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/assets.py b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/assets.py index 155b49726f6e4..b592f416f2e07 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/assets.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/assets.py @@ -21,6 +21,7 @@ from airflow.api_fastapi.common.db.common import SessionDep from airflow.api_fastapi.common.router import AirflowRouter +from airflow.api_fastapi.core_api.datamodels.ui.assets import NextRunAssetsResponse from airflow.api_fastapi.core_api.security import requires_access_asset, requires_access_dag from airflow.models import DagModel from airflow.models.asset import ( @@ -38,11 +39,14 @@ @assets_router.get( "/next_run_assets/{dag_id}", dependencies=[Depends(requires_access_asset(method="GET")), Depends(requires_access_dag(method="GET"))], + # ``pending_partition_count`` is only meaningful for partitioned DAGs, so it is left unset + # otherwise; excluding unset fields keeps the response shape unchanged for non-partitioned DAGs. + response_model_exclude_unset=True, ) def next_run_assets( dag_id: str, session: SessionDep, -) -> dict: +) -> NextRunAssetsResponse: dag_model = DagModel.get_dagmodel(dag_id, session=session) if dag_model is None: raise HTTPException(status.HTTP_404_NOT_FOUND, f"Dag with id {dag_id} was not found") @@ -118,4 +122,4 @@ def next_run_assets( data: dict = {"asset_expression": dag_model.asset_expression, "events": events} if pending_partition_count is not None: data["pending_partition_count"] = pending_partition_count - return data + return NextRunAssetsResponse.model_validate(data) diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts index f124d321a1f88..c9ceeceb3da47 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts @@ -162,7 +162,7 @@ export const ensureUseAssetServiceGetDagAssetQueuedEventData = (queryClient: Que * Next Run Assets * @param data The data for the request. * @param data.dagId -* @returns unknown Successful Response +* @returns NextRunAssetsResponse Successful Response * @throws ApiError */ export const ensureUseAssetServiceNextRunAssetsData = (queryClient: QueryClient, { dagId }: { diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts index 5bdcfe667b228..2303a0e43cf53 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts @@ -162,7 +162,7 @@ export const prefetchUseAssetServiceGetDagAssetQueuedEvent = (queryClient: Query * Next Run Assets * @param data The data for the request. * @param data.dagId -* @returns unknown Successful Response +* @returns NextRunAssetsResponse Successful Response * @throws ApiError */ export const prefetchUseAssetServiceNextRunAssets = (queryClient: QueryClient, { dagId }: { diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts index 8c0976ec328e9..dfeac71a35dd5 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts @@ -162,7 +162,7 @@ export const useAssetServiceGetDagAssetQueuedEvent = = unknown[]>({ dagId }: { diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts index e11772395f0f6..3cbebd1166439 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts @@ -162,7 +162,7 @@ export const useAssetServiceGetDagAssetQueuedEventSuspense = = unknown[]>({ dagId }: { diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts index e03a9e6bada44..977430cbcc563 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts @@ -287,6 +287,164 @@ export const $AssetEventResponse = { description: 'Asset event serializer for responses.' } as const; +export const $AssetExpressionAlias = { + properties: { + alias: { + '$ref': '#/components/schemas/AssetExpressionAliasInfo' + } + }, + type: 'object', + required: ['alias'], + title: 'AssetExpressionAlias', + description: 'An asset alias leaf: ``{"alias": {"name": ..., "group": ...}}``.' +} as const; + +export const $AssetExpressionAliasInfo = { + properties: { + name: { + type: 'string', + title: 'Name' + }, + group: { + type: 'string', + title: 'Group' + } + }, + type: 'object', + required: ['name', 'group'], + title: 'AssetExpressionAliasInfo', + description: 'Body of an ``alias`` leaf node.' +} as const; + +export const $AssetExpressionAll = { + properties: { + all: { + items: { + oneOf: [ + { + '$ref': '#/components/schemas/AssetExpressionAsset' + }, + { + '$ref': '#/components/schemas/AssetExpressionAlias' + }, + { + '$ref': '#/components/schemas/AssetExpressionRef' + }, + { + '$ref': '#/components/schemas/AssetExpressionAny' + }, + { + '$ref': '#/components/schemas/AssetExpressionAll' + } + ] + }, + type: 'array', + title: 'All' + } + }, + type: 'object', + required: ['all'], + title: 'AssetExpressionAll', + description: 'An "and" node: ``{"all": [...]}`` -- satisfied when all children are satisfied.' +} as const; + +export const $AssetExpressionAny = { + properties: { + any: { + items: { + oneOf: [ + { + '$ref': '#/components/schemas/AssetExpressionAsset' + }, + { + '$ref': '#/components/schemas/AssetExpressionAlias' + }, + { + '$ref': '#/components/schemas/AssetExpressionRef' + }, + { + '$ref': '#/components/schemas/AssetExpressionAny' + }, + { + '$ref': '#/components/schemas/AssetExpressionAll' + } + ] + }, + type: 'array', + title: 'Any' + } + }, + type: 'object', + required: ['any'], + title: 'AssetExpressionAny', + description: 'An "or" node: ``{"any": [...]}`` -- satisfied when any child is satisfied.' +} as const; + +export const $AssetExpressionAsset = { + properties: { + asset: { + '$ref': '#/components/schemas/AssetExpressionAssetInfo' + } + }, + type: 'object', + required: ['asset'], + title: 'AssetExpressionAsset', + description: 'An asset leaf: ``{"asset": {"uri": ..., "name": ..., "group": ...}}``.' +} as const; + +export const $AssetExpressionAssetInfo = { + properties: { + uri: { + type: 'string', + title: 'Uri' + }, + name: { + type: 'string', + title: 'Name' + }, + group: { + type: 'string', + title: 'Group' + }, + id: { + anyOf: [ + { + type: 'integer' + }, + { + type: 'null' + } + ], + title: 'Id' + } + }, + type: 'object', + required: ['uri', 'name', 'group'], + title: 'AssetExpressionAssetInfo', + description: `Body of an \`\`asset\`\` leaf node. + +\`\`id\`\` is injected by \`\`DagModelOperation.update_dag_asset_expression\`\` when the expression is +persisted; \`\`BaseAsset.as_expression()\`\` itself only emits \`\`uri\`\`/\`\`name\`\`/\`\`group\`\`. It is left +optional so a row persisted before id-enrichment (or migrated from the pre-3.0 dataset format) +degrades gracefully instead of failing response validation.` +} as const; + +export const $AssetExpressionRef = { + properties: { + asset_ref: { + additionalProperties: { + type: 'string' + }, + type: 'object', + title: 'Asset Ref' + } + }, + type: 'object', + required: ['asset_ref'], + title: 'AssetExpressionRef', + description: 'An unresolved asset reference leaf: ``{"asset_ref": {"name": ...}}`` or ``{"asset_ref": {"uri": ...}}``.' +} as const; + export const $AssetResponse = { properties: { id: { @@ -2284,8 +2442,23 @@ export const $DAGDetailsResponse = { asset_expression: { anyOf: [ { - additionalProperties: true, - type: 'object' + oneOf: [ + { + '$ref': '#/components/schemas/AssetExpressionAsset' + }, + { + '$ref': '#/components/schemas/AssetExpressionAlias' + }, + { + '$ref': '#/components/schemas/AssetExpressionRef' + }, + { + '$ref': '#/components/schemas/AssetExpressionAny' + }, + { + '$ref': '#/components/schemas/AssetExpressionAll' + } + ] }, { type: 'null' @@ -8366,8 +8539,23 @@ export const $DAGWithLatestDagRunsResponse = { asset_expression: { anyOf: [ { - additionalProperties: true, - type: 'object' + oneOf: [ + { + '$ref': '#/components/schemas/AssetExpressionAsset' + }, + { + '$ref': '#/components/schemas/AssetExpressionAlias' + }, + { + '$ref': '#/components/schemas/AssetExpressionRef' + }, + { + '$ref': '#/components/schemas/AssetExpressionAny' + }, + { + '$ref': '#/components/schemas/AssetExpressionAll' + } + ] }, { type: 'null' @@ -9148,6 +9336,93 @@ export const $MenuItemCollectionResponse = { description: 'Menu Item Collection serializer for responses.' } as const; +export const $NextRunAssetEventResponse = { + properties: { + id: { + type: 'integer', + title: 'Id' + }, + uri: { + type: 'string', + title: 'Uri' + }, + name: { + type: 'string', + title: 'Name' + }, + lastUpdate: { + anyOf: [ + { + type: 'string', + format: 'date-time' + }, + { + type: 'null' + } + ], + title: 'Lastupdate' + } + }, + type: 'object', + required: ['id', 'uri', 'name'], + title: 'NextRunAssetEventResponse', + description: "An asset, and the time of its latest event, that a DAG's next run is waiting on." +} as const; + +export const $NextRunAssetsResponse = { + properties: { + asset_expression: { + anyOf: [ + { + oneOf: [ + { + '$ref': '#/components/schemas/AssetExpressionAsset' + }, + { + '$ref': '#/components/schemas/AssetExpressionAlias' + }, + { + '$ref': '#/components/schemas/AssetExpressionRef' + }, + { + '$ref': '#/components/schemas/AssetExpressionAny' + }, + { + '$ref': '#/components/schemas/AssetExpressionAll' + } + ] + }, + { + type: 'null' + } + ], + title: 'Asset Expression' + }, + events: { + items: { + '$ref': '#/components/schemas/NextRunAssetEventResponse' + }, + type: 'array', + title: 'Events' + }, + pending_partition_count: { + anyOf: [ + { + type: 'integer' + }, + { + type: 'null' + } + ], + title: 'Pending Partition Count' + } + }, + type: 'object', + required: ['events'], + title: 'NextRunAssetsResponse', + description: "Assets feeding a DAG's next run, with the scheduling expression that combines them." +} as const; + export const $NodeResponse = { properties: { id: { @@ -9289,8 +9564,23 @@ export const $PartitionedDagRunCollectionResponse = { additionalProperties: { anyOf: [ { - additionalProperties: true, - type: 'object' + oneOf: [ + { + '$ref': '#/components/schemas/AssetExpressionAsset' + }, + { + '$ref': '#/components/schemas/AssetExpressionAlias' + }, + { + '$ref': '#/components/schemas/AssetExpressionRef' + }, + { + '$ref': '#/components/schemas/AssetExpressionAny' + }, + { + '$ref': '#/components/schemas/AssetExpressionAll' + } + ] }, { type: 'null' @@ -9377,8 +9667,23 @@ export const $PartitionedDagRunDetailResponse = { asset_expression: { anyOf: [ { - additionalProperties: true, - type: 'object' + oneOf: [ + { + '$ref': '#/components/schemas/AssetExpressionAsset' + }, + { + '$ref': '#/components/schemas/AssetExpressionAlias' + }, + { + '$ref': '#/components/schemas/AssetExpressionRef' + }, + { + '$ref': '#/components/schemas/AssetExpressionAny' + }, + { + '$ref': '#/components/schemas/AssetExpressionAll' + } + ] }, { type: 'null' diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts index e65f313e1d93f..013f3a105a3b9 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts @@ -3,7 +3,7 @@ import type { CancelablePromise } from './core/CancelablePromise'; import { OpenAPI } from './core/OpenAPI'; import { request as __request } from './core/request'; -import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData, GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse, GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData, CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse, GetAssetQueuedEventsData, GetAssetQueuedEventsResponse, DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData, GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse, DeleteDagAssetQueuedEventsData, DeleteDagAssetQueuedEventsResponse, GetDagAssetQueuedEventData, GetDagAssetQueuedEventResponse, DeleteDagAssetQueuedEventData, DeleteDagAssetQueuedEventResponse, NextRunAssetsData, NextRunAssetsResponse, ListBackfillsData, ListBackfillsResponse, CreateBackfillData, CreateBackfillResponse, GetBackfillData, GetBackfillResponse, PauseBackfillData, PauseBackfillResponse, UnpauseBackfillData, UnpauseBackfillResponse, CancelBackfillData, CancelBackfillResponse, CreateBackfillDryRunData, CreateBackfillDryRunResponse, ListBackfillsUiData, ListBackfillsUiResponse, DeleteConnectionData, DeleteConnectionResponse, GetConnectionData, GetConnectionResponse, PatchConnectionData, PatchConnectionResponse, GetConnectionsData, GetConnectionsResponse, PostConnectionData, PostConnectionResponse, BulkConnectionsData, BulkConnectionsResponse, TestConnectionData, TestConnectionResponse, CreateDefaultConnectionsResponse, HookMetaDataResponse, GetDagRunData, GetDagRunResponse, DeleteDagRunData, DeleteDagRunResponse, PatchDagRunData, PatchDagRunResponse, BulkDagRunsData, BulkDagRunsResponse, GetDagRunsData, GetDagRunsResponse, TriggerDagRunData, TriggerDagRunResponse, GetUpstreamAssetEventsData, GetUpstreamAssetEventsResponse, ClearDagRunData, ClearDagRunResponse, WaitDagRunUntilFinishedData, WaitDagRunUntilFinishedResponse, GetListDagRunsBatchData, GetListDagRunsBatchResponse, GetDagRunStatsData, GetDagRunStatsResponse, GetDagSourceData, GetDagSourceResponse, GetDagStatsData, GetDagStatsResponse, GetConfigData, GetConfigResponse, GetConfigValueData, GetConfigValueResponse, GetConfigsResponse, ListDagWarningsData, ListDagWarningsResponse, GetDagsData, GetDagsResponse, PatchDagsData, PatchDagsResponse, GetDagData, GetDagResponse, PatchDagData, PatchDagResponse, DeleteDagData, DeleteDagResponse, GetDagDetailsData, GetDagDetailsResponse, FavoriteDagData, FavoriteDagResponse, UnfavoriteDagData, UnfavoriteDagResponse, GetDagTagsData, GetDagTagsResponse, GetDagsUiData, GetDagsUiResponse, GetLatestRunInfoData, GetLatestRunInfoResponse, GetEventLogData, GetEventLogResponse, GetEventLogsData, GetEventLogsResponse, GetExtraLinksData, GetExtraLinksResponse, GetTaskInstanceData, GetTaskInstanceResponse, PatchTaskInstanceData, PatchTaskInstanceResponse, DeleteTaskInstanceData, DeleteTaskInstanceResponse, GetMappedTaskInstancesData, GetMappedTaskInstancesResponse, GetTaskInstanceDependenciesByMapIndexData, GetTaskInstanceDependenciesByMapIndexResponse, GetTaskInstanceDependenciesData, GetTaskInstanceDependenciesResponse, GetTaskInstanceTriesData, GetTaskInstanceTriesResponse, GetMappedTaskInstanceTriesData, GetMappedTaskInstanceTriesResponse, GetMappedTaskInstanceData, GetMappedTaskInstanceResponse, PatchTaskInstanceByMapIndexData, PatchTaskInstanceByMapIndexResponse, GetTaskInstancesData, GetTaskInstancesResponse, BulkTaskInstancesData, BulkTaskInstancesResponse, GetTaskInstancesBatchData, GetTaskInstancesBatchResponse, GetTaskInstanceTryDetailsData, GetTaskInstanceTryDetailsResponse, GetMappedTaskInstanceTryDetailsData, GetMappedTaskInstanceTryDetailsResponse, PostClearTaskInstancesData, PostClearTaskInstancesResponse, PatchTaskGroupInstancesData, PatchTaskGroupInstancesResponse, PatchTaskGroupInstancesDryRunData, PatchTaskGroupInstancesDryRunResponse, PatchTaskInstanceDryRunByMapIndexData, PatchTaskInstanceDryRunByMapIndexResponse, PatchTaskInstanceDryRunData, PatchTaskInstanceDryRunResponse, GetLogData, GetLogResponse, GetExternalLogUrlData, GetExternalLogUrlResponse, UpdateHitlDetailData, UpdateHitlDetailResponse, GetHitlDetailData, GetHitlDetailResponse, GetHitlDetailTryDetailData, GetHitlDetailTryDetailResponse, GetHitlDetailsData, GetHitlDetailsResponse, GetImportErrorData, GetImportErrorResponse, GetImportErrorsData, GetImportErrorsResponse, GetJobsData, GetJobsResponse, GetPluginsData, GetPluginsResponse, ImportErrorsResponse, DeletePoolData, DeletePoolResponse, GetPoolData, GetPoolResponse, PatchPoolData, PatchPoolResponse, GetPoolsData, GetPoolsResponse, PostPoolData, PostPoolResponse, BulkPoolsData, BulkPoolsResponse, GetProvidersData, GetProvidersResponse, ListAssetStatesData, ListAssetStatesResponse, ClearAssetStateData, ClearAssetStateResponse, GetAssetStateData, GetAssetStateResponse, SetAssetStateData, SetAssetStateResponse, DeleteAssetStateData, DeleteAssetStateResponse, ListTaskStatesData, ListTaskStatesResponse, ClearTaskStateData, ClearTaskStateResponse, GetTaskStateData, GetTaskStateResponse, SetTaskStateData, SetTaskStateResponse, DeleteTaskStateData, DeleteTaskStateResponse, GetXcomEntryData, GetXcomEntryResponse, UpdateXcomEntryData, UpdateXcomEntryResponse, DeleteXcomEntryData, DeleteXcomEntryResponse, GetXcomEntriesData, GetXcomEntriesResponse, CreateXcomEntryData, CreateXcomEntryResponse, GetTasksData, GetTasksResponse, GetTaskData, GetTaskResponse, DeleteVariableData, DeleteVariableResponse, GetVariableData, GetVariableResponse, PatchVariableData, PatchVariableResponse, GetVariablesData, GetVariablesResponse, PostVariableData, PostVariableResponse, BulkVariablesData, BulkVariablesResponse, ReparseDagFileData, ReparseDagFileResponse, GetDagVersionData, GetDagVersionResponse, GetDagVersionsData, GetDagVersionsResponse, GetHealthResponse, GetVersionResponse, LoginData, LoginResponse, LogoutResponse, GetAuthMenusResponse, GetCurrentUserInfoResponse, GenerateTokenData, GenerateTokenResponse2, GetPartitionedDagRunsData, GetPartitionedDagRunsResponse, GetPendingPartitionedDagRunData, GetPendingPartitionedDagRunResponse, GetDependenciesData, GetDependenciesResponse, HistoricalMetricsData, HistoricalMetricsResponse, DagStatsResponse2, GetDeadlinesData, GetDeadlinesResponse, GetDagDeadlineAlertsData, GetDagDeadlineAlertsResponse, StructureDataData, StructureDataResponse2, GetDagStructureData, GetDagStructureResponse, GetGridRunsData, GetGridRunsResponse, GetGridTiSummariesStreamData, GetGridTiSummariesStreamResponse, GetGanttDataData, GetGanttDataResponse, GetCalendarData, GetCalendarResponse, ListTeamsData, ListTeamsResponse } from './types.gen'; +import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData, GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse, GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData, CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse, GetAssetQueuedEventsData, GetAssetQueuedEventsResponse, DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData, GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse, DeleteDagAssetQueuedEventsData, DeleteDagAssetQueuedEventsResponse, GetDagAssetQueuedEventData, GetDagAssetQueuedEventResponse, DeleteDagAssetQueuedEventData, DeleteDagAssetQueuedEventResponse, NextRunAssetsData, NextRunAssetsResponse2, ListBackfillsData, ListBackfillsResponse, CreateBackfillData, CreateBackfillResponse, GetBackfillData, GetBackfillResponse, PauseBackfillData, PauseBackfillResponse, UnpauseBackfillData, UnpauseBackfillResponse, CancelBackfillData, CancelBackfillResponse, CreateBackfillDryRunData, CreateBackfillDryRunResponse, ListBackfillsUiData, ListBackfillsUiResponse, DeleteConnectionData, DeleteConnectionResponse, GetConnectionData, GetConnectionResponse, PatchConnectionData, PatchConnectionResponse, GetConnectionsData, GetConnectionsResponse, PostConnectionData, PostConnectionResponse, BulkConnectionsData, BulkConnectionsResponse, TestConnectionData, TestConnectionResponse, CreateDefaultConnectionsResponse, HookMetaDataResponse, GetDagRunData, GetDagRunResponse, DeleteDagRunData, DeleteDagRunResponse, PatchDagRunData, PatchDagRunResponse, BulkDagRunsData, BulkDagRunsResponse, GetDagRunsData, GetDagRunsResponse, TriggerDagRunData, TriggerDagRunResponse, GetUpstreamAssetEventsData, GetUpstreamAssetEventsResponse, ClearDagRunData, ClearDagRunResponse, WaitDagRunUntilFinishedData, WaitDagRunUntilFinishedResponse, GetListDagRunsBatchData, GetListDagRunsBatchResponse, GetDagRunStatsData, GetDagRunStatsResponse, GetDagSourceData, GetDagSourceResponse, GetDagStatsData, GetDagStatsResponse, GetConfigData, GetConfigResponse, GetConfigValueData, GetConfigValueResponse, GetConfigsResponse, ListDagWarningsData, ListDagWarningsResponse, GetDagsData, GetDagsResponse, PatchDagsData, PatchDagsResponse, GetDagData, GetDagResponse, PatchDagData, PatchDagResponse, DeleteDagData, DeleteDagResponse, GetDagDetailsData, GetDagDetailsResponse, FavoriteDagData, FavoriteDagResponse, UnfavoriteDagData, UnfavoriteDagResponse, GetDagTagsData, GetDagTagsResponse, GetDagsUiData, GetDagsUiResponse, GetLatestRunInfoData, GetLatestRunInfoResponse, GetEventLogData, GetEventLogResponse, GetEventLogsData, GetEventLogsResponse, GetExtraLinksData, GetExtraLinksResponse, GetTaskInstanceData, GetTaskInstanceResponse, PatchTaskInstanceData, PatchTaskInstanceResponse, DeleteTaskInstanceData, DeleteTaskInstanceResponse, GetMappedTaskInstancesData, GetMappedTaskInstancesResponse, GetTaskInstanceDependenciesByMapIndexData, GetTaskInstanceDependenciesByMapIndexResponse, GetTaskInstanceDependenciesData, GetTaskInstanceDependenciesResponse, GetTaskInstanceTriesData, GetTaskInstanceTriesResponse, GetMappedTaskInstanceTriesData, GetMappedTaskInstanceTriesResponse, GetMappedTaskInstanceData, GetMappedTaskInstanceResponse, PatchTaskInstanceByMapIndexData, PatchTaskInstanceByMapIndexResponse, GetTaskInstancesData, GetTaskInstancesResponse, BulkTaskInstancesData, BulkTaskInstancesResponse, GetTaskInstancesBatchData, GetTaskInstancesBatchResponse, GetTaskInstanceTryDetailsData, GetTaskInstanceTryDetailsResponse, GetMappedTaskInstanceTryDetailsData, GetMappedTaskInstanceTryDetailsResponse, PostClearTaskInstancesData, PostClearTaskInstancesResponse, PatchTaskGroupInstancesData, PatchTaskGroupInstancesResponse, PatchTaskGroupInstancesDryRunData, PatchTaskGroupInstancesDryRunResponse, PatchTaskInstanceDryRunByMapIndexData, PatchTaskInstanceDryRunByMapIndexResponse, PatchTaskInstanceDryRunData, PatchTaskInstanceDryRunResponse, GetLogData, GetLogResponse, GetExternalLogUrlData, GetExternalLogUrlResponse, UpdateHitlDetailData, UpdateHitlDetailResponse, GetHitlDetailData, GetHitlDetailResponse, GetHitlDetailTryDetailData, GetHitlDetailTryDetailResponse, GetHitlDetailsData, GetHitlDetailsResponse, GetImportErrorData, GetImportErrorResponse, GetImportErrorsData, GetImportErrorsResponse, GetJobsData, GetJobsResponse, GetPluginsData, GetPluginsResponse, ImportErrorsResponse, DeletePoolData, DeletePoolResponse, GetPoolData, GetPoolResponse, PatchPoolData, PatchPoolResponse, GetPoolsData, GetPoolsResponse, PostPoolData, PostPoolResponse, BulkPoolsData, BulkPoolsResponse, GetProvidersData, GetProvidersResponse, ListAssetStatesData, ListAssetStatesResponse, ClearAssetStateData, ClearAssetStateResponse, GetAssetStateData, GetAssetStateResponse, SetAssetStateData, SetAssetStateResponse, DeleteAssetStateData, DeleteAssetStateResponse, ListTaskStatesData, ListTaskStatesResponse, ClearTaskStateData, ClearTaskStateResponse, GetTaskStateData, GetTaskStateResponse, SetTaskStateData, SetTaskStateResponse, DeleteTaskStateData, DeleteTaskStateResponse, GetXcomEntryData, GetXcomEntryResponse, UpdateXcomEntryData, UpdateXcomEntryResponse, DeleteXcomEntryData, DeleteXcomEntryResponse, GetXcomEntriesData, GetXcomEntriesResponse, CreateXcomEntryData, CreateXcomEntryResponse, GetTasksData, GetTasksResponse, GetTaskData, GetTaskResponse, DeleteVariableData, DeleteVariableResponse, GetVariableData, GetVariableResponse, PatchVariableData, PatchVariableResponse, GetVariablesData, GetVariablesResponse, PostVariableData, PostVariableResponse, BulkVariablesData, BulkVariablesResponse, ReparseDagFileData, ReparseDagFileResponse, GetDagVersionData, GetDagVersionResponse, GetDagVersionsData, GetDagVersionsResponse, GetHealthResponse, GetVersionResponse, LoginData, LoginResponse, LogoutResponse, GetAuthMenusResponse, GetCurrentUserInfoResponse, GenerateTokenData, GenerateTokenResponse2, GetPartitionedDagRunsData, GetPartitionedDagRunsResponse, GetPendingPartitionedDagRunData, GetPendingPartitionedDagRunResponse, GetDependenciesData, GetDependenciesResponse, HistoricalMetricsData, HistoricalMetricsResponse, DagStatsResponse2, GetDeadlinesData, GetDeadlinesResponse, GetDagDeadlineAlertsData, GetDagDeadlineAlertsResponse, StructureDataData, StructureDataResponse2, GetDagStructureData, GetDagStructureResponse, GetGridRunsData, GetGridRunsResponse, GetGridTiSummariesStreamData, GetGridTiSummariesStreamResponse, GetGanttDataData, GetGanttDataResponse, GetCalendarData, GetCalendarResponse, ListTeamsData, ListTeamsResponse } from './types.gen'; export class AssetService { /** @@ -411,10 +411,10 @@ export class AssetService { * Next Run Assets * @param data The data for the request. * @param data.dagId - * @returns unknown Successful Response + * @returns NextRunAssetsResponse Successful Response * @throws ApiError */ - public static nextRunAssets(data: NextRunAssetsData): CancelablePromise { + public static nextRunAssets(data: NextRunAssetsData): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/ui/next_run_assets/{dag_id}', diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts index 85a37588df6fd..ee95e4d54f547 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts @@ -75,6 +75,66 @@ export type AssetEventResponse = { partition_key?: string | null; }; +/** + * An asset alias leaf: ``{"alias": {"name": ..., "group": ...}}``. + */ +export type AssetExpressionAlias = { + alias: AssetExpressionAliasInfo; +}; + +/** + * Body of an ``alias`` leaf node. + */ +export type AssetExpressionAliasInfo = { + name: string; + group: string; +}; + +/** + * An "and" node: ``{"all": [...]}`` -- satisfied when all children are satisfied. + */ +export type AssetExpressionAll = { + all: Array<(AssetExpressionAsset | AssetExpressionAlias | AssetExpressionRef | AssetExpressionAny | AssetExpressionAll)>; +}; + +/** + * An "or" node: ``{"any": [...]}`` -- satisfied when any child is satisfied. + */ +export type AssetExpressionAny = { + any: Array<(AssetExpressionAsset | AssetExpressionAlias | AssetExpressionRef | AssetExpressionAny | AssetExpressionAll)>; +}; + +/** + * An asset leaf: ``{"asset": {"uri": ..., "name": ..., "group": ...}}``. + */ +export type AssetExpressionAsset = { + asset: AssetExpressionAssetInfo; +}; + +/** + * Body of an ``asset`` leaf node. + * + * ``id`` is injected by ``DagModelOperation.update_dag_asset_expression`` when the expression is + * persisted; ``BaseAsset.as_expression()`` itself only emits ``uri``/``name``/``group``. It is left + * optional so a row persisted before id-enrichment (or migrated from the pre-3.0 dataset format) + * degrades gracefully instead of failing response validation. + */ +export type AssetExpressionAssetInfo = { + uri: string; + name: string; + group: string; + id?: number | null; +}; + +/** + * An unresolved asset reference leaf: ``{"asset_ref": {"name": ...}}`` or ``{"asset_ref": {"uri": ...}}``. + */ +export type AssetExpressionRef = { + asset_ref: { + [key: string]: (string); + }; +}; + /** * Asset serializer for responses. */ @@ -644,9 +704,7 @@ export type DAGDetailsResponse = { owners: Array<(string)>; catchup: boolean; dag_run_timeout: string | null; - asset_expression: { - [key: string]: unknown; -} | null; + asset_expression: AssetExpressionAsset | AssetExpressionAlias | AssetExpressionRef | AssetExpressionAny | AssetExpressionAll | null; doc_md: string | null; start_date: string | null; end_date: string | null; @@ -2088,9 +2146,7 @@ export type DAGWithLatestDagRunsResponse = { next_dagrun_run_after: string | null; allowed_run_types: Array | null; owners: Array<(string)>; - asset_expression: { - [key: string]: unknown; -} | null; + asset_expression: AssetExpressionAsset | AssetExpressionAlias | AssetExpressionRef | AssetExpressionAny | AssetExpressionAll | null; latest_dag_runs: Array; pending_actions: Array; is_favorite: boolean; @@ -2312,6 +2368,25 @@ export type MenuItemCollectionResponse = { extra_menu_items: Array; }; +/** + * An asset, and the time of its latest event, that a DAG's next run is waiting on. + */ +export type NextRunAssetEventResponse = { + id: number; + uri: string; + name: string; + lastUpdate?: string | null; +}; + +/** + * Assets feeding a DAG's next run, with the scheduling expression that combines them. + */ +export type NextRunAssetsResponse = { + asset_expression?: AssetExpressionAsset | AssetExpressionAlias | AssetExpressionRef | AssetExpressionAny | AssetExpressionAll | null; + events: Array; + pending_partition_count?: number | null; +}; + /** * Node serializer for responses. */ @@ -2346,9 +2421,7 @@ export type PartitionedDagRunCollectionResponse = { partitioned_dag_runs: Array; total: number; asset_expressions?: { - [key: string]: ({ - [key: string]: unknown; -} | null); + [key: string]: (AssetExpressionAsset | AssetExpressionAlias | AssetExpressionRef | AssetExpressionAny | AssetExpressionAll | null); } | null; }; @@ -2365,9 +2438,7 @@ export type PartitionedDagRunDetailResponse = { assets: Array; total_required: number; total_received: number; - asset_expression?: { - [key: string]: unknown; -} | null; + asset_expression?: AssetExpressionAsset | AssetExpressionAlias | AssetExpressionRef | AssetExpressionAny | AssetExpressionAll | null; }; /** @@ -2648,9 +2719,7 @@ export type NextRunAssetsData = { dagId: string; }; -export type NextRunAssetsResponse = { - [key: string]: unknown; -}; +export type NextRunAssetsResponse2 = NextRunAssetsResponse; export type ListBackfillsData = { dagId: string; @@ -4735,9 +4804,7 @@ export type $OpenApiTs = { /** * Successful Response */ - 200: { - [key: string]: unknown; - }; + 200: NextRunAssetsResponse; /** * Validation Error */ diff --git a/airflow-core/src/airflow/ui/src/components/AssetExpression/AssetExpression.tsx b/airflow-core/src/airflow/ui/src/components/AssetExpression/AssetExpression.tsx index 9b2a8e3b4f2b8..4d686a5152961 100644 --- a/airflow-core/src/airflow/ui/src/components/AssetExpression/AssetExpression.tsx +++ b/airflow-core/src/airflow/ui/src/components/AssetExpression/AssetExpression.tsx @@ -43,7 +43,7 @@ export const AssetExpression = ({ <> {"any" in expression ? ( - {expression.any?.map((item, index) => ( + {expression.any.map((item, index) => ( // eslint-disable-next-line react/no-array-index-key {"asset" in item || "alias" in item ? ( @@ -54,7 +54,7 @@ export const AssetExpression = ({ ) : ( )} - {expression.any && index === expression.any.length - 1 ? undefined : ( + {index === expression.any.length - 1 ? undefined : ( {translate("expression.or")} @@ -66,7 +66,7 @@ export const AssetExpression = ({ ) : undefined} {"all" in expression ? ( - {expression.all?.map((item, index) => ( + {expression.all.map((item, index) => ( // eslint-disable-next-line react/no-array-index-key {"asset" in item || "alias" in item ? ( diff --git a/airflow-core/src/airflow/ui/src/components/AssetExpression/types.ts b/airflow-core/src/airflow/ui/src/components/AssetExpression/types.ts index 7328dcfcc0eec..ea2b2cb7ae1b1 100644 --- a/airflow-core/src/airflow/ui/src/components/AssetExpression/types.ts +++ b/airflow-core/src/airflow/ui/src/components/AssetExpression/types.ts @@ -16,31 +16,25 @@ * specific language governing permissions and limitations * under the License. */ +import type { + AssetExpressionAlias, + AssetExpressionAll, + AssetExpressionAny, + AssetExpressionAsset, + AssetExpressionRef, +} from "openapi/requests/types.gen"; -type Asset = { - asset: { - group: string; - id: number; - name: string; - uri: string; - }; -}; +// `asset_expression` is now typed on the API side (see the AssetExpression* models in the Python +// `datamodels/common.py`), so these aliases are derived from the generated client instead of being +// hand-maintained here. The previous local union could drift from the server shape with no runtime check. -type Alias = { - alias: { - group: string; - name: string; - }; -}; +export type NextRunEvent = { id: number; lastUpdate?: string | null; name: string | null; uri: string }; -export type NextRunEvent = { id: number; lastUpdate: string | null; name: string | null; uri: string }; - -export type AssetSummary = Alias | Asset; +export type AssetSummary = AssetExpressionAlias | AssetExpressionAsset; export type ExpressionType = - | Alias - | Asset - | { - all?: Array; - any?: Array; - }; + | AssetExpressionAlias + | AssetExpressionAll + | AssetExpressionAny + | AssetExpressionAsset + | AssetExpressionRef; diff --git a/airflow-core/src/airflow/ui/src/components/AssetProgressCell.tsx b/airflow-core/src/airflow/ui/src/components/AssetProgressCell.tsx index 7c877ba0a4620..91188e391bd6b 100644 --- a/airflow-core/src/airflow/ui/src/components/AssetProgressCell.tsx +++ b/airflow-core/src/airflow/ui/src/components/AssetProgressCell.tsx @@ -21,7 +21,7 @@ import { FiDatabase } from "react-icons/fi"; import { usePartitionedDagRunServiceGetPendingPartitionedDagRun } from "openapi/queries"; import type { PartitionedDagRunAssetResponse } from "openapi/requests/types.gen"; -import { AssetExpression, type ExpressionType } from "src/components/AssetExpression"; +import { AssetExpression } from "src/components/AssetExpression"; import type { NextRunEvent } from "src/components/AssetExpression/types"; import { Popover } from "src/components/ui"; @@ -35,7 +35,7 @@ type Props = { export const AssetProgressCell = ({ dagId, partitionKey, totalReceived, totalRequired }: Props) => { const { data, isLoading } = usePartitionedDagRunServiceGetPendingPartitionedDagRun({ dagId, partitionKey }); - const assetExpression = data?.asset_expression as ExpressionType | undefined; + const assetExpression = data?.asset_expression ?? undefined; const assets: Array = data?.assets ?? []; const events: Array = assets diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/AssetSchedule.tsx b/airflow-core/src/airflow/ui/src/pages/DagsList/AssetSchedule.tsx index d1c27c56ce658..8b174d593d00c 100644 --- a/airflow-core/src/airflow/ui/src/pages/DagsList/AssetSchedule.tsx +++ b/airflow-core/src/airflow/ui/src/pages/DagsList/AssetSchedule.tsx @@ -68,7 +68,7 @@ export const AssetSchedule = ({ assetExpression, dagId, timetablePartitioned, ti { enabled: !timetablePartitioned }, ); - const nextRunEvents = (nextRun?.events ?? []) as Array; + const nextRunEvents: Array = nextRun?.events ?? []; const queuedAssetEvents = new Map(); if (!timetablePartitioned) { @@ -103,7 +103,7 @@ export const AssetSchedule = ({ assetExpression, dagId, timetablePartitioned, ti } if (timetablePartitioned) { - const pendingCount = (nextRun?.pending_partition_count as number | undefined) ?? 0; + const pendingCount = nextRun?.pending_partition_count ?? 0; if (pendingCount === 0) { return ( @@ -148,7 +148,7 @@ export const AssetSchedule = ({ assetExpression, dagId, timetablePartitioned, ti diff --git a/airflow-core/tests/unit/api_fastapi/core_api/datamodels/test_common.py b/airflow-core/tests/unit/api_fastapi/core_api/datamodels/test_common.py new file mode 100644 index 0000000000000..2cddea1654947 --- /dev/null +++ b/airflow-core/tests/unit/api_fastapi/core_api/datamodels/test_common.py @@ -0,0 +1,77 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from __future__ import annotations + +import pytest +from pydantic import TypeAdapter, ValidationError + +from airflow.api_fastapi.core_api.datamodels.common import AssetExpression + +# A single adapter is enough to validate/serialize the discriminated union. +_adapter: TypeAdapter[AssetExpression] = TypeAdapter(AssetExpression) + +# Leaf nodes as actually stored in ``DagModel.asset_expression``: ``asset`` leaves are +# enriched with the resolved ``AssetModel.id`` (see +# ``DagModelOperation.update_dag_asset_expression``), while ``alias`` and ``asset_ref`` +# leaves are left as ``BaseAsset.as_expression()`` produced them. +_ASSET = {"asset": {"uri": "s3://bucket/key", "name": "my_asset", "group": "asset", "id": 7}} +_ALIAS = {"alias": {"name": "my_alias", "group": "asset"}} +_REF_BY_NAME = {"asset_ref": {"name": "by_name"}} +_REF_BY_URI = {"asset_ref": {"uri": "s3://bucket/key"}} + + +@pytest.mark.parametrize( + "expression", + [ + pytest.param(_ASSET, id="asset"), + pytest.param(_ALIAS, id="alias"), + pytest.param(_REF_BY_NAME, id="asset_ref_by_name"), + pytest.param(_REF_BY_URI, id="asset_ref_by_uri"), + pytest.param({"any": [_ASSET, _ALIAS]}, id="any"), + pytest.param({"all": [_ASSET, _REF_BY_NAME]}, id="all"), + pytest.param({"all": [{"any": [_ASSET]}, _ASSET]}, id="nested"), + ], +) +def test_asset_expression_round_trips_unchanged(expression: dict): + """The typed model must accept and re-serialize each stored expression byte-identically.""" + validated = _adapter.validate_python(expression) + assert _adapter.dump_python(validated, by_alias=True) == expression + + +def test_asset_expression_tolerates_legacy_asset_leaf_without_id(): + """ + ``asset`` leaves written by the current scheduler always carry ``id``, but a row persisted + before id-enrichment (or migrated from the pre-3.0 dataset format) may not. Such a leaf must + still validate -- with ``id`` defaulting to ``None`` -- so the public endpoints that serve a + stored expression degrade gracefully instead of returning a 500. + """ + validated = _adapter.validate_python({"asset": {"uri": "s3://b", "name": "n", "group": "asset"}}) + assert validated.asset.id is None + + +@pytest.mark.parametrize( + "invalid", + [ + pytest.param({}, id="empty"), + pytest.param({"unknown": {}}, id="unknown_key"), + pytest.param({"asset": _ASSET["asset"], "alias": _ALIAS["alias"]}, id="ambiguous_two_keys"), + pytest.param({"asset": {"name": "a", "id": 1}}, id="asset_missing_fields"), + ], +) +def test_asset_expression_rejects_invalid_shapes(invalid: dict): + with pytest.raises(ValidationError): + _adapter.validate_python(invalid) diff --git a/airflow-ctl/src/airflowctl/api/datamodels/generated.py b/airflow-ctl/src/airflowctl/api/datamodels/generated.py index 44ac8d3938751..f880802917c3b 100644 --- a/airflow-ctl/src/airflowctl/api/datamodels/generated.py +++ b/airflow-ctl/src/airflowctl/api/datamodels/generated.py @@ -49,6 +49,39 @@ class AssetAliasResponse(BaseModel): group: Annotated[str, Field(title="Group")] +class AssetExpressionAliasInfo(BaseModel): + """ + Body of an ``alias`` leaf node. + """ + + name: Annotated[str, Field(title="Name")] + group: Annotated[str, Field(title="Group")] + + +class AssetExpressionAssetInfo(BaseModel): + """ + Body of an ``asset`` leaf node. + + ``id`` is injected by ``DagModelOperation.update_dag_asset_expression`` when the expression is + persisted; ``BaseAsset.as_expression()`` itself only emits ``uri``/``name``/``group``. It is left + optional so a row persisted before id-enrichment (or migrated from the pre-3.0 dataset format) + degrades gracefully instead of failing response validation. + """ + + uri: Annotated[str, Field(title="Uri")] + name: Annotated[str, Field(title="Name")] + group: Annotated[str, Field(title="Group")] + id: Annotated[int | None, Field(title="Id")] = None + + +class AssetExpressionRef(BaseModel): + """ + An unresolved asset reference leaf: ``{"asset_ref": {"name": ...}}`` or ``{"asset_ref": {"uri": ...}}``. + """ + + asset_ref: Annotated[dict[str, str], Field(title="Asset Ref")] + + class AssetWatcherResponse(BaseModel): """ Asset watcher serializer for responses. @@ -1184,6 +1217,22 @@ class AssetEventResponse(BaseModel): partition_key: Annotated[str | None, Field(title="Partition Key")] = None +class AssetExpressionAlias(BaseModel): + """ + An asset alias leaf: ``{"alias": {"name": ..., "group": ...}}``. + """ + + alias: AssetExpressionAliasInfo + + +class AssetExpressionAsset(BaseModel): + """ + An asset leaf: ``{"asset": {"uri": ..., "name": ..., "group": ...}}``. + """ + + asset: AssetExpressionAssetInfo + + class AssetResponse(BaseModel): """ Asset serializer for responses. @@ -1482,76 +1531,6 @@ class ConnectionCollectionResponse(BaseModel): total_entries: Annotated[int, Field(title="Total Entries")] -class DAGDetailsResponse(BaseModel): - """ - Specific serializer for Dag Details responses. - """ - - dag_id: Annotated[str, Field(title="Dag Id")] - dag_display_name: Annotated[str, Field(title="Dag Display Name")] - is_paused: Annotated[bool, Field(title="Is Paused")] - is_stale: Annotated[bool, Field(title="Is Stale")] - last_parsed_time: Annotated[datetime | None, Field(title="Last Parsed Time")] = None - last_parse_duration: Annotated[float | None, Field(title="Last Parse Duration")] = None - last_expired: Annotated[datetime | None, Field(title="Last Expired")] = None - bundle_name: Annotated[str | None, Field(title="Bundle Name")] = None - bundle_version: Annotated[str | None, Field(title="Bundle Version")] = None - relative_fileloc: Annotated[str | None, Field(title="Relative Fileloc")] = None - fileloc: Annotated[str, Field(title="Fileloc")] - description: Annotated[str | None, Field(title="Description")] = None - timetable_summary: Annotated[str | None, Field(title="Timetable Summary")] = None - timetable_description: Annotated[str | None, Field(title="Timetable Description")] = None - timetable_partitioned: Annotated[bool, Field(title="Timetable Partitioned")] - timetable_periodic: Annotated[bool, Field(title="Timetable Periodic")] - tags: Annotated[list[DagTagResponse], Field(title="Tags")] - max_active_tasks: Annotated[int, Field(title="Max Active Tasks")] - max_active_runs: Annotated[int | None, Field(title="Max Active Runs")] = None - max_consecutive_failed_dag_runs: Annotated[int, Field(title="Max Consecutive Failed Dag Runs")] - has_task_concurrency_limits: Annotated[bool, Field(title="Has Task Concurrency Limits")] - has_import_errors: Annotated[bool, Field(title="Has Import Errors")] - next_dagrun_logical_date: Annotated[datetime | None, Field(title="Next Dagrun Logical Date")] = None - next_dagrun_data_interval_start: Annotated[ - datetime | None, Field(title="Next Dagrun Data Interval Start") - ] = None - next_dagrun_data_interval_end: Annotated[ - datetime | None, Field(title="Next Dagrun Data Interval End") - ] = None - next_dagrun_run_after: Annotated[datetime | None, Field(title="Next Dagrun Run After")] = None - allowed_run_types: Annotated[list[DagRunType] | None, Field(title="Allowed Run Types")] = None - owners: Annotated[list[str], Field(title="Owners")] - catchup: Annotated[bool, Field(title="Catchup")] - dag_run_timeout: Annotated[timedelta | None, Field(title="Dag Run Timeout")] = None - asset_expression: Annotated[dict[str, Any] | None, Field(title="Asset Expression")] = None - doc_md: Annotated[str | None, Field(title="Doc Md")] = None - start_date: Annotated[datetime | None, Field(title="Start Date")] = None - end_date: Annotated[datetime | None, Field(title="End Date")] = None - is_paused_upon_creation: Annotated[bool | None, Field(title="Is Paused Upon Creation")] = None - params: Annotated[dict[str, Any] | None, Field(title="Params")] = None - render_template_as_native_obj: Annotated[bool, Field(title="Render Template As Native Obj")] - template_search_path: Annotated[list[str] | None, Field(title="Template Search Path")] = None - timezone: Annotated[str | None, Field(title="Timezone")] = None - last_parsed: Annotated[datetime | None, Field(title="Last Parsed")] = None - default_args: Annotated[dict[str, Any] | None, Field(title="Default Args")] = None - rerun_with_latest_version: Annotated[bool | None, Field(title="Rerun With Latest Version")] = None - owner_links: Annotated[dict[str, str] | None, Field(title="Owner Links")] = None - is_favorite: Annotated[bool | None, Field(title="Is Favorite")] = False - active_runs_count: Annotated[int | None, Field(title="Active Runs Count")] = 0 - is_backfillable: Annotated[ - bool, Field(description="Whether this Dag's schedule supports backfilling.", title="Is Backfillable") - ] - file_token: Annotated[str, Field(description="Return file token.", title="File Token")] - concurrency: Annotated[ - int, - Field( - description="Return max_active_tasks as concurrency.\n\nDeprecated: Use max_active_tasks instead.", - title="Concurrency", - ), - ] - latest_dag_version: Annotated[ - DagVersionResponse | None, Field(description="Return the latest DagVersion.") - ] = None - - class DAGResponse(BaseModel): """ Dag serializer for responses. @@ -2295,3 +2274,118 @@ class BulkBodyBulkTaskInstanceBody(BaseModel): ], Field(title="Actions"), ] + + +class AssetExpressionAll(BaseModel): + """ + An "and" node: ``{"all": [...]}`` -- satisfied when all children are satisfied. + """ + + all: Annotated[ + list[ + AssetExpressionAsset + | AssetExpressionAlias + | AssetExpressionRef + | AssetExpressionAny + | AssetExpressionAll + ], + Field(title="All"), + ] + + +class AssetExpressionAny(BaseModel): + """ + An "or" node: ``{"any": [...]}`` -- satisfied when any child is satisfied. + """ + + any: Annotated[ + list[ + AssetExpressionAsset + | AssetExpressionAlias + | AssetExpressionRef + | AssetExpressionAny + | AssetExpressionAll + ], + Field(title="Any"), + ] + + +class DAGDetailsResponse(BaseModel): + """ + Specific serializer for Dag Details responses. + """ + + dag_id: Annotated[str, Field(title="Dag Id")] + dag_display_name: Annotated[str, Field(title="Dag Display Name")] + is_paused: Annotated[bool, Field(title="Is Paused")] + is_stale: Annotated[bool, Field(title="Is Stale")] + last_parsed_time: Annotated[datetime | None, Field(title="Last Parsed Time")] = None + last_parse_duration: Annotated[float | None, Field(title="Last Parse Duration")] = None + last_expired: Annotated[datetime | None, Field(title="Last Expired")] = None + bundle_name: Annotated[str | None, Field(title="Bundle Name")] = None + bundle_version: Annotated[str | None, Field(title="Bundle Version")] = None + relative_fileloc: Annotated[str | None, Field(title="Relative Fileloc")] = None + fileloc: Annotated[str, Field(title="Fileloc")] + description: Annotated[str | None, Field(title="Description")] = None + timetable_summary: Annotated[str | None, Field(title="Timetable Summary")] = None + timetable_description: Annotated[str | None, Field(title="Timetable Description")] = None + timetable_partitioned: Annotated[bool, Field(title="Timetable Partitioned")] + timetable_periodic: Annotated[bool, Field(title="Timetable Periodic")] + tags: Annotated[list[DagTagResponse], Field(title="Tags")] + max_active_tasks: Annotated[int, Field(title="Max Active Tasks")] + max_active_runs: Annotated[int | None, Field(title="Max Active Runs")] = None + max_consecutive_failed_dag_runs: Annotated[int, Field(title="Max Consecutive Failed Dag Runs")] + has_task_concurrency_limits: Annotated[bool, Field(title="Has Task Concurrency Limits")] + has_import_errors: Annotated[bool, Field(title="Has Import Errors")] + next_dagrun_logical_date: Annotated[datetime | None, Field(title="Next Dagrun Logical Date")] = None + next_dagrun_data_interval_start: Annotated[ + datetime | None, Field(title="Next Dagrun Data Interval Start") + ] = None + next_dagrun_data_interval_end: Annotated[ + datetime | None, Field(title="Next Dagrun Data Interval End") + ] = None + next_dagrun_run_after: Annotated[datetime | None, Field(title="Next Dagrun Run After")] = None + allowed_run_types: Annotated[list[DagRunType] | None, Field(title="Allowed Run Types")] = None + owners: Annotated[list[str], Field(title="Owners")] + catchup: Annotated[bool, Field(title="Catchup")] + dag_run_timeout: Annotated[timedelta | None, Field(title="Dag Run Timeout")] = None + asset_expression: Annotated[ + AssetExpressionAsset + | AssetExpressionAlias + | AssetExpressionRef + | AssetExpressionAny + | AssetExpressionAll + | None, + Field(title="Asset Expression"), + ] = None + doc_md: Annotated[str | None, Field(title="Doc Md")] = None + start_date: Annotated[datetime | None, Field(title="Start Date")] = None + end_date: Annotated[datetime | None, Field(title="End Date")] = None + is_paused_upon_creation: Annotated[bool | None, Field(title="Is Paused Upon Creation")] = None + params: Annotated[dict[str, Any] | None, Field(title="Params")] = None + render_template_as_native_obj: Annotated[bool, Field(title="Render Template As Native Obj")] + template_search_path: Annotated[list[str] | None, Field(title="Template Search Path")] = None + timezone: Annotated[str | None, Field(title="Timezone")] = None + last_parsed: Annotated[datetime | None, Field(title="Last Parsed")] = None + default_args: Annotated[dict[str, Any] | None, Field(title="Default Args")] = None + rerun_with_latest_version: Annotated[bool | None, Field(title="Rerun With Latest Version")] = None + owner_links: Annotated[dict[str, str] | None, Field(title="Owner Links")] = None + is_favorite: Annotated[bool | None, Field(title="Is Favorite")] = False + active_runs_count: Annotated[int | None, Field(title="Active Runs Count")] = 0 + is_backfillable: Annotated[ + bool, Field(description="Whether this Dag's schedule supports backfilling.", title="Is Backfillable") + ] + file_token: Annotated[str, Field(description="Return file token.", title="File Token")] + concurrency: Annotated[ + int, + Field( + description="Return max_active_tasks as concurrency.\n\nDeprecated: Use max_active_tasks instead.", + title="Concurrency", + ), + ] + latest_dag_version: Annotated[ + DagVersionResponse | None, Field(description="Return the latest DagVersion.") + ] = None + + +AssetExpressionAll.model_rebuild() From 2eb0faf2fdb38c0e062a8aa6664ad4f1b546a6c7 Mon Sep 17 00:00:00 2001 From: Anuragp22 Date: Fri, 29 May 2026 16:05:25 +0000 Subject: [PATCH 2/2] Rename newsfragment to PR number --- .../{67692.improvement.rst => 67725.improvement.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename airflow-core/newsfragments/{67692.improvement.rst => 67725.improvement.rst} (100%) diff --git a/airflow-core/newsfragments/67692.improvement.rst b/airflow-core/newsfragments/67725.improvement.rst similarity index 100% rename from airflow-core/newsfragments/67692.improvement.rst rename to airflow-core/newsfragments/67725.improvement.rst