This is an automated email from the ASF dual-hosted git repository. pierrejeambrun pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push: new 4c5ad9c846 AIP-84 post variable (#42948) 4c5ad9c846 is described below commit 4c5ad9c846262b8f5fe64669fcb36a60e3d330ae Author: Pierre Jeambrun <pierrejb...@gmail.com> AuthorDate: Tue Oct 15 03:26:35 2024 +0800 AIP-84 post variable (#42948) --- .../api_connexion/endpoints/variable_endpoint.py | 1 + airflow/api_fastapi/openapi/v1-generated.yaml | 38 ++++++++++++++++ airflow/api_fastapi/views/public/variables.py | 17 +++++++- airflow/ui/openapi-gen/queries/common.ts | 3 ++ airflow/ui/openapi-gen/queries/queries.ts | 39 +++++++++++++++++ airflow/ui/openapi-gen/requests/services.gen.ts | 26 +++++++++++ airflow/ui/openapi-gen/requests/types.gen.ts | 29 +++++++++++++ tests/api_fastapi/views/public/test_variables.py | 50 ++++++++++++++++++++++ 8 files changed, 201 insertions(+), 2 deletions(-) diff --git a/airflow/api_connexion/endpoints/variable_endpoint.py b/airflow/api_connexion/endpoints/variable_endpoint.py index b1d9e2f5c8..20e7ce1ede 100644 --- a/airflow/api_connexion/endpoints/variable_endpoint.py +++ b/airflow/api_connexion/endpoints/variable_endpoint.py @@ -130,6 +130,7 @@ def patch_variable( return variable_schema.dump(variable) +@mark_fastapi_migration_done @security.requires_access_variable("POST") @action_logging( event=action_event_from_permission( diff --git a/airflow/api_fastapi/openapi/v1-generated.yaml b/airflow/api_fastapi/openapi/v1-generated.yaml index 759ab7fdd8..235410a6d3 100644 --- a/airflow/api_fastapi/openapi/v1-generated.yaml +++ b/airflow/api_fastapi/openapi/v1-generated.yaml @@ -695,6 +695,44 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' + /public/variables/: + post: + tags: + - Variable + summary: Post Variable + description: Create a variable. + operationId: post_variable + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VariableBody' + required: true + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/VariableResponse' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPExceptionResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPExceptionResponse' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' /public/dags/{dag_id}/dagRuns/{dag_run_id}: get: tags: diff --git a/airflow/api_fastapi/views/public/variables.py b/airflow/api_fastapi/views/public/variables.py index b4c07e23de..a61b9bb930 100644 --- a/airflow/api_fastapi/views/public/variables.py +++ b/airflow/api_fastapi/views/public/variables.py @@ -73,10 +73,23 @@ async def patch_variable( if not variable: raise HTTPException(404, f"The Variable with key: `{variable_key}` was not found") if update_mask: - data = patch_body.dict(include=set(update_mask) - non_update_fields) + data = patch_body.model_dump(include=set(update_mask) - non_update_fields) else: - data = patch_body.dict(exclude=non_update_fields) + data = patch_body.model_dump(exclude=non_update_fields) for key, val in data.items(): setattr(variable, key, val) session.add(variable) return variable + + +@variables_router.post("/", status_code=201, responses=create_openapi_http_exception_doc([401, 403])) +async def post_variable( + post_body: VariableBody, + session: Annotated[Session, Depends(get_session)], +) -> VariableResponse: + """Create a variable.""" + Variable.set(**post_body.model_dump(), session=session) + + variable = session.scalar(select(Variable).where(Variable.key == post_body.key).limit(1)) + + return VariableResponse.model_validate(variable, from_attributes=True) diff --git a/airflow/ui/openapi-gen/queries/common.ts b/airflow/ui/openapi-gen/queries/common.ts index e3c0ef3ab4..426e28447f 100644 --- a/airflow/ui/openapi-gen/queries/common.ts +++ b/airflow/ui/openapi-gen/queries/common.ts @@ -185,6 +185,9 @@ export const UseDagRunServiceGetDagRunKeyFn = ( }, queryKey?: Array<unknown>, ) => [useDagRunServiceGetDagRunKey, ...(queryKey ?? [{ dagId, dagRunId }])]; +export type VariableServicePostVariableMutationResult = Awaited< + ReturnType<typeof VariableService.postVariable> +>; export type DagServicePatchDagsMutationResult = Awaited< ReturnType<typeof DagService.patchDags> >; diff --git a/airflow/ui/openapi-gen/queries/queries.ts b/airflow/ui/openapi-gen/queries/queries.ts index b4c8cf9fea..557a7ba8ff 100644 --- a/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow/ui/openapi-gen/queries/queries.ts @@ -295,6 +295,45 @@ export const useDagRunServiceGetDagRun = < queryFn: () => DagRunService.getDagRun({ dagId, dagRunId }) as TData, ...options, }); +/** + * Post Variable + * Create a variable. + * @param data The data for the request. + * @param data.requestBody + * @returns VariableResponse Successful Response + * @throws ApiError + */ +export const useVariableServicePostVariable = < + TData = Common.VariableServicePostVariableMutationResult, + TError = unknown, + TContext = unknown, +>( + options?: Omit< + UseMutationOptions< + TData, + TError, + { + requestBody: VariableBody; + }, + TContext + >, + "mutationFn" + >, +) => + useMutation< + TData, + TError, + { + requestBody: VariableBody; + }, + TContext + >({ + mutationFn: ({ requestBody }) => + VariableService.postVariable({ + requestBody, + }) as unknown as Promise<TData>, + ...options, + }); /** * Patch Dags * Patch multiple DAGs. diff --git a/airflow/ui/openapi-gen/requests/services.gen.ts b/airflow/ui/openapi-gen/requests/services.gen.ts index 43d9e8d940..78b113c7f2 100644 --- a/airflow/ui/openapi-gen/requests/services.gen.ts +++ b/airflow/ui/openapi-gen/requests/services.gen.ts @@ -27,6 +27,8 @@ import type { GetVariableResponse, PatchVariableData, PatchVariableResponse, + PostVariableData, + PostVariableResponse, GetDagRunData, GetDagRunResponse, DeleteDagRunData, @@ -400,6 +402,30 @@ export class VariableService { }, }); } + + /** + * Post Variable + * Create a variable. + * @param data The data for the request. + * @param data.requestBody + * @returns VariableResponse Successful Response + * @throws ApiError + */ + public static postVariable( + data: PostVariableData, + ): CancelablePromise<PostVariableResponse> { + return __request(OpenAPI, { + method: "POST", + url: "/public/variables/", + body: data.requestBody, + mediaType: "application/json", + errors: { + 401: "Unauthorized", + 403: "Forbidden", + 422: "Validation Error", + }, + }); + } } export class DagRunService { diff --git a/airflow/ui/openapi-gen/requests/types.gen.ts b/airflow/ui/openapi-gen/requests/types.gen.ts index 3deba451fc..856517d560 100644 --- a/airflow/ui/openapi-gen/requests/types.gen.ts +++ b/airflow/ui/openapi-gen/requests/types.gen.ts @@ -365,6 +365,12 @@ export type PatchVariableData = { export type PatchVariableResponse = VariableResponse; +export type PostVariableData = { + requestBody: VariableBody; +}; + +export type PostVariableResponse = VariableResponse; + export type GetDagRunData = { dagId: string; dagRunId: string; @@ -684,6 +690,29 @@ export type $OpenApiTs = { }; }; }; + "/public/variables/": { + post: { + req: PostVariableData; + res: { + /** + * Successful Response + */ + 201: VariableResponse; + /** + * Unauthorized + */ + 401: HTTPExceptionResponse; + /** + * Forbidden + */ + 403: HTTPExceptionResponse; + /** + * Validation Error + */ + 422: HTTPValidationError; + }; + }; + }; "/public/dags/{dag_id}/dagRuns/{dag_run_id}": { get: { req: GetDagRunData; diff --git a/tests/api_fastapi/views/public/test_variables.py b/tests/api_fastapi/views/public/test_variables.py index cf5c78fd56..58a09538a9 100644 --- a/tests/api_fastapi/views/public/test_variables.py +++ b/tests/api_fastapi/views/public/test_variables.py @@ -223,3 +223,53 @@ class TestPatchVariable(TestVariableEndpoint): assert response.status_code == 404 body = response.json() assert f"The Variable with key: `{TEST_VARIABLE_KEY}` was not found" == body["detail"] + + +class TestPostVariable(TestVariableEndpoint): + @pytest.mark.enable_redact + @pytest.mark.parametrize( + "body, expected_response", + [ + ( + { + "key": "new variable key", + "value": "new variable value", + "description": "new variable description", + }, + { + "key": "new variable key", + "value": "new variable value", + "description": "new variable description", + }, + ), + ( + { + "key": "another_password", + "value": "password_value", + "description": "another password", + }, + { + "key": "another_password", + "value": "***", + "description": "another password", + }, + ), + ( + { + "key": "another value with sensitive information", + "value": '{"password": "new_password"}', + "description": "some description", + }, + { + "key": "another value with sensitive information", + "value": '{"password": "***"}', + "description": "some description", + }, + ), + ], + ) + def test_post_should_respond_201(self, test_client, session, body, expected_response): + self.create_variable() + response = test_client.post("/public/variables/", json=body) + assert response.status_code == 201 + assert response.json() == expected_response