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

Reply via email to