This is an automated email from the ASF dual-hosted git repository.
yuqi4733 pushed a commit to branch branch-1.0
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.0 by this push:
new 6eac0dfcd4 [#8641] feat(clients): Add Java and Python client API for
job template alteration (#8802)
6eac0dfcd4 is described below
commit 6eac0dfcd4a0c30b2979dc09ad3ab91ef7bbfdf7
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Oct 13 16:39:38 2025 +0800
[#8641] feat(clients): Add Java and Python client API for job template
alteration (#8802)
### What changes were proposed in this pull request?
This PR adds the Java and Python client API for job template alteration.
### Why are the changes needed?
This is a part of work for job template alteration
Fix: #8641
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
Add UTs.
Co-authored-by: Jerry Shao <[email protected]>
Co-authored-by: Mini Yu <[email protected]>
---
.../org/apache/gravitino/client/DTOConverters.java | 57 +++++++++++
.../apache/gravitino/client/GravitinoClient.java | 7 ++
.../apache/gravitino/client/GravitinoMetalake.java | 29 ++++++
.../apache/gravitino/client/TestSupportsJobs.java | 57 +++++++++++
.../gravitino/client/dto_converters.py | 62 +++++++++++
.../gravitino/client/gravitino_client.py | 19 ++++
.../gravitino/client/gravitino_metalake.py | 40 ++++++++
.../gravitino/dto/job/shell_template_update_dto.py | 56 ++++++++++
.../gravitino/dto/job/spark_template_update_dto.py | 76 ++++++++++++++
.../gravitino/dto/job/template_update_dto.py | 44 ++++++++
.../dto/requests/job_template_update_request.py | 113 +++++++++++++++++++++
.../dto/requests/job_template_updates_request.py | 38 +++++++
.../tests/unittests/test_supports_jobs.py | 26 +++++
13 files changed, 624 insertions(+)
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/DTOConverters.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/DTOConverters.java
index 4657644e1f..5b8aad2417 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/DTOConverters.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/DTOConverters.java
@@ -38,9 +38,13 @@ import org.apache.gravitino.dto.authorization.PrivilegeDTO;
import org.apache.gravitino.dto.authorization.SecurableObjectDTO;
import org.apache.gravitino.dto.job.JobTemplateDTO;
import org.apache.gravitino.dto.job.ShellJobTemplateDTO;
+import org.apache.gravitino.dto.job.ShellTemplateUpdateDTO;
import org.apache.gravitino.dto.job.SparkJobTemplateDTO;
+import org.apache.gravitino.dto.job.SparkTemplateUpdateDTO;
+import org.apache.gravitino.dto.job.TemplateUpdateDTO;
import org.apache.gravitino.dto.requests.CatalogUpdateRequest;
import org.apache.gravitino.dto.requests.FilesetUpdateRequest;
+import org.apache.gravitino.dto.requests.JobTemplateUpdateRequest;
import org.apache.gravitino.dto.requests.MetalakeUpdateRequest;
import org.apache.gravitino.dto.requests.ModelUpdateRequest;
import org.apache.gravitino.dto.requests.ModelVersionUpdateRequest;
@@ -51,6 +55,7 @@ import org.apache.gravitino.dto.requests.TagUpdateRequest;
import org.apache.gravitino.dto.requests.TopicUpdateRequest;
import org.apache.gravitino.file.FilesetChange;
import org.apache.gravitino.job.JobTemplate;
+import org.apache.gravitino.job.JobTemplateChange;
import org.apache.gravitino.job.ShellJobTemplate;
import org.apache.gravitino.job.SparkJobTemplate;
import org.apache.gravitino.messaging.TopicChange;
@@ -494,4 +499,56 @@ class DTOConverters {
throw new IllegalArgumentException("Unsupported job type: " +
jobTemplate.jobType());
}
}
+
+ static JobTemplateUpdateRequest toJobTemplateUpdateRequest(JobTemplateChange
change) {
+ if (change instanceof JobTemplateChange.RenameJobTemplate) {
+ return new JobTemplateUpdateRequest.RenameJobTemplateRequest(
+ ((JobTemplateChange.RenameJobTemplate) change).getNewName());
+
+ } else if (change instanceof JobTemplateChange.UpdateJobTemplateComment) {
+ return new JobTemplateUpdateRequest.UpdateJobTemplateCommentRequest(
+ ((JobTemplateChange.UpdateJobTemplateComment)
change).getNewComment());
+
+ } else if (change instanceof JobTemplateChange.UpdateJobTemplate) {
+ return new JobTemplateUpdateRequest.UpdateJobTemplateContentRequest(
+ toTemplateUpdateDTO(((JobTemplateChange.UpdateJobTemplate)
change).getTemplateUpdate()));
+
+ } else {
+ throw new IllegalArgumentException(
+ "Unknown change type: " + change.getClass().getSimpleName());
+ }
+ }
+
+ static TemplateUpdateDTO
toTemplateUpdateDTO(JobTemplateChange.TemplateUpdate change) {
+ if (change instanceof JobTemplateChange.ShellTemplateUpdate) {
+ JobTemplateChange.ShellTemplateUpdate shellUpdate =
+ (JobTemplateChange.ShellTemplateUpdate) change;
+ return ShellTemplateUpdateDTO.builder()
+ .withNewExecutable(shellUpdate.getNewExecutable())
+ .withNewArguments(shellUpdate.getNewArguments())
+ .withNewEnvironments(shellUpdate.getNewEnvironments())
+ .withNewCustomFields(shellUpdate.getNewCustomFields())
+ .withNewScripts(shellUpdate.getNewScripts())
+ .build();
+
+ } else if (change instanceof JobTemplateChange.SparkTemplateUpdate) {
+ JobTemplateChange.SparkTemplateUpdate sparkUpdate =
+ (JobTemplateChange.SparkTemplateUpdate) change;
+ return SparkTemplateUpdateDTO.builder()
+ .withNewExecutable(sparkUpdate.getNewExecutable())
+ .withNewArguments(sparkUpdate.getNewArguments())
+ .withNewEnvironments(sparkUpdate.getNewEnvironments())
+ .withNewCustomFields(sparkUpdate.getNewCustomFields())
+ .withNewClassName(sparkUpdate.getNewClassName())
+ .withNewJars(sparkUpdate.getNewJars())
+ .withNewFiles(sparkUpdate.getNewFiles())
+ .withNewArchives(sparkUpdate.getNewArchives())
+ .withNewConfigs(sparkUpdate.getNewConfigs())
+ .build();
+
+ } else {
+ throw new IllegalArgumentException(
+ "Unknown template update type: " +
change.getClass().getSimpleName());
+ }
+ }
}
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
index f22a334452..4083f2e5ff 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
@@ -61,6 +61,7 @@ import
org.apache.gravitino.exceptions.TagAlreadyExistsException;
import org.apache.gravitino.exceptions.UserAlreadyExistsException;
import org.apache.gravitino.job.JobHandle;
import org.apache.gravitino.job.JobTemplate;
+import org.apache.gravitino.job.JobTemplateChange;
import org.apache.gravitino.job.SupportsJobs;
import org.apache.gravitino.policy.Policy;
import org.apache.gravitino.policy.PolicyChange;
@@ -594,6 +595,12 @@ public class GravitinoClient extends GravitinoClientBase
return getMetalake().deleteJobTemplate(jobTemplateName);
}
+ @Override
+ public JobTemplate alterJobTemplate(String jobTemplateName,
JobTemplateChange... changes)
+ throws NoSuchJobTemplateException, IllegalArgumentException {
+ return getMetalake().alterJobTemplate(jobTemplateName, changes);
+ }
+
@Override
public List<JobHandle> listJobs(String jobTemplateName) throws
NoSuchJobTemplateException {
return getMetalake().listJobs(jobTemplateName);
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
index ae01555de0..0c3c43b442 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
@@ -56,6 +56,8 @@ import
org.apache.gravitino.dto.requests.CatalogUpdatesRequest;
import org.apache.gravitino.dto.requests.GroupAddRequest;
import org.apache.gravitino.dto.requests.JobRunRequest;
import org.apache.gravitino.dto.requests.JobTemplateRegisterRequest;
+import org.apache.gravitino.dto.requests.JobTemplateUpdateRequest;
+import org.apache.gravitino.dto.requests.JobTemplateUpdatesRequest;
import org.apache.gravitino.dto.requests.OwnerSetRequest;
import org.apache.gravitino.dto.requests.PolicyCreateRequest;
import org.apache.gravitino.dto.requests.PolicySetRequest;
@@ -119,6 +121,7 @@ import
org.apache.gravitino.exceptions.TagAlreadyExistsException;
import org.apache.gravitino.exceptions.UserAlreadyExistsException;
import org.apache.gravitino.job.JobHandle;
import org.apache.gravitino.job.JobTemplate;
+import org.apache.gravitino.job.JobTemplateChange;
import org.apache.gravitino.job.SupportsJobs;
import org.apache.gravitino.policy.Policy;
import org.apache.gravitino.policy.PolicyChange;
@@ -1467,6 +1470,32 @@ public class GravitinoMetalake extends MetalakeDTO
return resp.dropped();
}
+ @Override
+ public JobTemplate alterJobTemplate(String jobTemplateName,
JobTemplateChange... changes)
+ throws NoSuchJobTemplateException, IllegalArgumentException {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(jobTemplateName), "job template name must not
be null or empty");
+
+ List<JobTemplateUpdateRequest> updates =
+ Arrays.stream(changes)
+ .map(DTOConverters::toJobTemplateUpdateRequest)
+ .collect(Collectors.toList());
+ JobTemplateUpdatesRequest req = new JobTemplateUpdatesRequest(updates);
+
+ JobTemplateResponse resp =
+ restClient.put(
+ String.format(API_METALAKES_JOB_TEMPLATES_PATH,
RESTUtils.encodeString(this.name()))
+ + "/"
+ + RESTUtils.encodeString(jobTemplateName),
+ req,
+ JobTemplateResponse.class,
+ Collections.emptyMap(),
+ ErrorHandlers.jobErrorHandler());
+ resp.validate();
+
+ return
org.apache.gravitino.dto.util.DTOConverters.fromDTO(resp.getJobTemplate());
+ }
+
@Override
public List<JobHandle> listJobs(String jobTemplateName) throws
NoSuchJobTemplateException {
Preconditions.checkArgument(
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsJobs.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsJobs.java
index f51874f2f8..b73590b8a9 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsJobs.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsJobs.java
@@ -32,6 +32,8 @@ import org.apache.gravitino.dto.job.ShellJobTemplateDTO;
import org.apache.gravitino.dto.job.SparkJobTemplateDTO;
import org.apache.gravitino.dto.requests.JobRunRequest;
import org.apache.gravitino.dto.requests.JobTemplateRegisterRequest;
+import org.apache.gravitino.dto.requests.JobTemplateUpdateRequest;
+import org.apache.gravitino.dto.requests.JobTemplateUpdatesRequest;
import org.apache.gravitino.dto.responses.BaseResponse;
import org.apache.gravitino.dto.responses.DropResponse;
import org.apache.gravitino.dto.responses.ErrorResponse;
@@ -177,6 +179,61 @@ public class TestSupportsJobs extends TestBase {
InUseException.class, () ->
metalake.deleteJobTemplate(jobTemplateName));
}
+ @Test
+ public void testAlterJobTemplate() throws JsonProcessingException {
+ String jobTemplateName = "shell-job-template";
+ JobTemplateDTO templateDTO = newShellJobTemplateDTO(jobTemplateName);
+ JobTemplate expected =
org.apache.gravitino.dto.util.DTOConverters.fromDTO(templateDTO);
+ JobTemplateResponse resp = new JobTemplateResponse(templateDTO);
+
+ JobTemplateUpdateRequest rename =
+ new JobTemplateUpdateRequest.RenameJobTemplateRequest(jobTemplateName);
+ JobTemplateUpdatesRequest req = new
JobTemplateUpdatesRequest(Lists.newArrayList(rename));
+
+ buildMockResource(
+ Method.PUT, jobTemplatesPath() + "/" + jobTemplateName, req, resp,
HttpStatus.SC_OK);
+
+ JobTemplate actual = metalake.alterJobTemplate(jobTemplateName,
rename.jobTemplateChange());
+ Assertions.assertEquals(expected, actual);
+
+ // Test throw NoSuchJobTemplateException
+ ErrorResponse errorResp =
+
ErrorResponse.notFound(NoSuchJobTemplateException.class.getSimpleName(), "mock
error");
+ buildMockResource(
+ Method.PUT,
+ jobTemplatesPath() + "/" + jobTemplateName,
+ req,
+ errorResp,
+ HttpStatus.SC_NOT_FOUND);
+ Assertions.assertThrows(
+ NoSuchJobTemplateException.class,
+ () -> metalake.alterJobTemplate(jobTemplateName,
rename.jobTemplateChange()));
+
+ // Test throw IllegalArgumentException
+ ErrorResponse errorResp2 = ErrorResponse.illegalArguments("mock error");
+ buildMockResource(
+ Method.PUT,
+ jobTemplatesPath() + "/" + jobTemplateName,
+ req,
+ errorResp2,
+ HttpStatus.SC_BAD_REQUEST);
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> metalake.alterJobTemplate(jobTemplateName,
rename.jobTemplateChange()));
+
+ // Test throw RuntimeException
+ ErrorResponse errorResp3 = ErrorResponse.internalError("mock error");
+ buildMockResource(
+ Method.PUT,
+ jobTemplatesPath() + "/" + jobTemplateName,
+ req,
+ errorResp3,
+ HttpStatus.SC_INTERNAL_SERVER_ERROR);
+ Assertions.assertThrows(
+ RuntimeException.class,
+ () -> metalake.alterJobTemplate(jobTemplateName,
rename.jobTemplateChange()));
+ }
+
@Test
public void testListJobs() throws JsonProcessingException {
String jobTemplateName = "shell-job-template";
diff --git a/clients/client-python/gravitino/client/dto_converters.py
b/clients/client-python/gravitino/client/dto_converters.py
index ec35d537bb..5283075318 100644
--- a/clients/client-python/gravitino/client/dto_converters.py
+++ b/clients/client-python/gravitino/client/dto_converters.py
@@ -18,6 +18,15 @@
from gravitino.api.catalog import Catalog
from gravitino.api.catalog_change import CatalogChange
from gravitino.api.job.job_template import JobTemplate, JobType
+from gravitino.api.job.job_template_change import (
+ TemplateUpdate,
+ ShellTemplateUpdate,
+ SparkTemplateUpdate,
+ JobTemplateChange,
+ RenameJobTemplate,
+ UpdateJobTemplateComment,
+ UpdateJobTemplate,
+)
from gravitino.api.job.shell_job_template import ShellJobTemplate
from gravitino.api.job.spark_job_template import SparkJobTemplate
from gravitino.client.fileset_catalog import FilesetCatalog
@@ -25,8 +34,17 @@ from gravitino.client.generic_model_catalog import
GenericModelCatalog
from gravitino.dto.catalog_dto import CatalogDTO
from gravitino.dto.job.job_template_dto import JobTemplateDTO
from gravitino.dto.job.shell_job_template_dto import ShellJobTemplateDTO
+from gravitino.dto.job.shell_template_update_dto import ShellTemplateUpdateDTO
from gravitino.dto.job.spark_job_template_dto import SparkJobTemplateDTO
+from gravitino.dto.job.spark_template_update_dto import SparkTemplateUpdateDTO
+from gravitino.dto.job.template_update_dto import TemplateUpdateDTO
from gravitino.dto.requests.catalog_update_request import CatalogUpdateRequest
+from gravitino.dto.requests.job_template_update_request import (
+ JobTemplateUpdateRequest,
+ RenameJobTemplateRequest,
+ UpdateJobTemplateCommentRequest,
+ UpdateJobTemplateContentRequest,
+)
from gravitino.dto.requests.metalake_update_request import
MetalakeUpdateRequest
from gravitino.api.metalake_change import MetalakeChange
from gravitino.utils import HTTPClient
@@ -211,3 +229,47 @@ class DTOConverters:
)
raise ValueError(f"Unsupported job type: {type(template)}")
+
+ @staticmethod
+ def to_template_update_dto(template_update: TemplateUpdate) ->
TemplateUpdateDTO:
+ if isinstance(template_update, ShellTemplateUpdate):
+ return ShellTemplateUpdateDTO(
+ new_executable=template_update.get_new_executable(),
+ new_arguments=template_update.get_new_arguments(),
+ new_environments=template_update.get_new_environments(),
+ new_custom_fields=template_update.get_new_custom_fields(),
+ new_scripts=template_update.get_new_scripts(),
+ )
+
+ if isinstance(template_update, SparkTemplateUpdate):
+ return SparkTemplateUpdateDTO(
+ new_executable=template_update.get_new_executable(),
+ new_arguments=template_update.get_new_arguments(),
+ new_environments=template_update.get_new_environments(),
+ new_custom_fields=template_update.get_new_custom_fields(),
+ new_class_name=template_update.get_new_class_name(),
+ new_jars=template_update.get_new_jars(),
+ new_files=template_update.get_new_files(),
+ new_archives=template_update.get_new_archives(),
+ new_configs=template_update.get_new_configs(),
+ )
+
+ raise ValueError(f"Unsupported template update type:
{type(template_update)}")
+
+ @staticmethod
+ def to_job_template_update_request(
+ change: JobTemplateChange,
+ ) -> JobTemplateUpdateRequest:
+ if isinstance(change, RenameJobTemplate):
+ return RenameJobTemplateRequest(change.get_new_name())
+
+ if isinstance(change, UpdateJobTemplateComment):
+ return UpdateJobTemplateCommentRequest(change.get_new_comment())
+
+ if isinstance(change, UpdateJobTemplate):
+ template_update_dto = DTOConverters.to_template_update_dto(
+ change.get_template_update()
+ )
+ return UpdateJobTemplateContentRequest(template_update_dto)
+
+ raise ValueError(f"Unknown change type: {type(change).__name__}")
diff --git a/clients/client-python/gravitino/client/gravitino_client.py
b/clients/client-python/gravitino/client/gravitino_client.py
index ea27a86443..02b61cb774 100644
--- a/clients/client-python/gravitino/client/gravitino_client.py
+++ b/clients/client-python/gravitino/client/gravitino_client.py
@@ -21,6 +21,7 @@ from gravitino.api.catalog import Catalog
from gravitino.api.catalog_change import CatalogChange
from gravitino.api.job.job_handle import JobHandle
from gravitino.api.job.job_template import JobTemplate
+from gravitino.api.job.job_template_change import JobTemplateChange
from gravitino.api.job.supports_jobs import SupportsJobs
from gravitino.auth.auth_data_provider import AuthDataProvider
from gravitino.client.gravitino_client_base import GravitinoClientBase
@@ -163,6 +164,24 @@ class GravitinoClient(GravitinoClientBase, SupportsJobs):
"""
return self.get_metalake().delete_job_template(job_template_name)
+ def alter_job_template(
+ self, job_template_name: str, *changes: JobTemplateChange
+ ) -> JobTemplate:
+ """Alters a job template with the specified changes.
+
+ Args:
+ job_template_name: The name of the job template to alter.
+ changes: The changes to apply to the job template.
+
+ Returns:
+ The altered JobTemplate object.
+
+ Raises:
+ NoSuchJobTemplateException: If no job template with the specified
name exists.
+ IllegalArgumentException: If any of the changes cannot be applied.
+ """
+ return self.get_metalake().alter_job_template(job_template_name,
*changes)
+
def list_jobs(self, job_template_name: str = None) -> List[JobHandle]:
"""Lists all the jobs in the current metalake.
diff --git a/clients/client-python/gravitino/client/gravitino_metalake.py
b/clients/client-python/gravitino/client/gravitino_metalake.py
index 00c393537b..86ae92b812 100644
--- a/clients/client-python/gravitino/client/gravitino_metalake.py
+++ b/clients/client-python/gravitino/client/gravitino_metalake.py
@@ -22,6 +22,7 @@ from gravitino.api.catalog import Catalog
from gravitino.api.catalog_change import CatalogChange
from gravitino.api.job.job_handle import JobHandle
from gravitino.api.job.job_template import JobTemplate
+from gravitino.api.job.job_template_change import JobTemplateChange
from gravitino.api.job.supports_jobs import SupportsJobs
from gravitino.client.dto_converters import DTOConverters
from gravitino.client.generic_job_handle import GenericJobHandle
@@ -33,6 +34,9 @@ from gravitino.dto.requests.job_run_request import
JobRunRequest
from gravitino.dto.requests.job_template_register_request import (
JobTemplateRegisterRequest,
)
+from gravitino.dto.requests.job_template_updates_request import (
+ JobTemplateUpdatesRequest,
+)
from gravitino.dto.responses.catalog_list_response import CatalogListResponse
from gravitino.dto.responses.catalog_response import CatalogResponse
from gravitino.dto.responses.drop_response import DropResponse
@@ -365,6 +369,42 @@ class GravitinoMetalake(MetalakeDTO, SupportsJobs):
return drop_response.dropped()
+ def alter_job_template(
+ self, job_template_name: str, *changes: JobTemplateChange
+ ) -> JobTemplate:
+ """Alter the job template with specified name by applying the changes.
+
+ Args:
+ job_template_name: the name of the job template.
+ changes: the changes to apply to the job template.
+
+ Raises:
+ NoSuchJobTemplateException if the job template with specified name
does not exist.
+ IllegalArgumentException if the changes are invalid.
+
+ Returns:
+ the altered JobTemplate.
+ """
+
+ reqs = [
+ DTOConverters.to_job_template_update_request(change) for change in
changes
+ ]
+ updates_request = JobTemplateUpdatesRequest(reqs)
+
+ url = (
+
f"{self.API_METALAKES_JOB_TEMPLATES_PATH.format(encode_string(self.name()))}/"
+ f"{encode_string(job_template_name)}"
+ )
+ response = self.rest_client.put(
+ url, json=updates_request, error_handler=JOB_ERROR_HANDLER
+ )
+ job_template_response = JobTemplateResponse.from_json(
+ response.body, infer_missing=True
+ )
+ job_template_response.validate()
+
+ return
DTOConverters.from_job_template_dto(job_template_response.job_template())
+
def list_jobs(self, job_template_name: str = None) -> List[JobHandle]:
"""List all the jobs under this metalake.
diff --git
a/clients/client-python/gravitino/dto/job/shell_template_update_dto.py
b/clients/client-python/gravitino/dto/job/shell_template_update_dto.py
new file mode 100644
index 0000000000..1fd299f3af
--- /dev/null
+++ b/clients/client-python/gravitino/dto/job/shell_template_update_dto.py
@@ -0,0 +1,56 @@
+# 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 dataclasses import dataclass, field
+from typing import Optional, List
+
+from dataclasses_json import config
+
+from gravitino.api.job.job_template_change import TemplateUpdate,
ShellTemplateUpdate
+from gravitino.dto.job.template_update_dto import TemplateUpdateDTO
+
+
+@dataclass
+class ShellTemplateUpdateDTO(TemplateUpdateDTO):
+ """DTO for updating shell job templates."""
+
+ _new_scripts: Optional[List[str]] =
field(metadata=config(field_name="newScripts"))
+
+ def __init__(
+ self,
+ new_executable: Optional[str] = None,
+ new_arguments: Optional[List[str]] = None,
+ new_environments: Optional[dict] = None,
+ new_custom_fields: Optional[dict] = None,
+ new_scripts: Optional[List[str]] = None,
+ ):
+ super().__init__(
+ _type="shell",
+ _new_executable=new_executable,
+ _new_arguments=new_arguments,
+ _new_environments=new_environments,
+ _new_custom_fields=new_custom_fields,
+ )
+ self._new_scripts = new_scripts
+
+ def to_template_update(self) -> TemplateUpdate:
+ return ShellTemplateUpdate(
+ new_executable=self._new_executable,
+ new_arguments=self._new_arguments,
+ new_environments=self._new_environments,
+ new_custom_fields=self._new_custom_fields,
+ new_scripts=self._new_scripts,
+ )
diff --git
a/clients/client-python/gravitino/dto/job/spark_template_update_dto.py
b/clients/client-python/gravitino/dto/job/spark_template_update_dto.py
new file mode 100644
index 0000000000..741a2ba617
--- /dev/null
+++ b/clients/client-python/gravitino/dto/job/spark_template_update_dto.py
@@ -0,0 +1,76 @@
+# 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 dataclasses import dataclass, field
+from typing import Optional, List, Dict
+
+from dataclasses_json import config
+
+from gravitino.api.job.job_template_change import TemplateUpdate,
SparkTemplateUpdate
+from gravitino.dto.job.template_update_dto import TemplateUpdateDTO
+
+
+@dataclass
+class SparkTemplateUpdateDTO(TemplateUpdateDTO):
+ """DTO for updating Spark job templates."""
+
+ _new_class_name: Optional[str] =
field(metadata=config(field_name="newClassName"))
+ _new_jars: Optional[List[str]] =
field(metadata=config(field_name="newJars"))
+ _new_files: Optional[List[str]] =
field(metadata=config(field_name="newFiles"))
+ _new_archives: Optional[List[str]] = field(
+ metadata=config(field_name="newArchives")
+ )
+ _new_configs: Optional[Dict[str, str]] = field(
+ metadata=config(field_name="newConfigs")
+ )
+
+ def __init__(
+ self,
+ new_executable: Optional[str] = None,
+ new_arguments: Optional[List[str]] = None,
+ new_environments: Optional[dict] = None,
+ new_custom_fields: Optional[dict] = None,
+ new_class_name: Optional[str] = None,
+ new_jars: Optional[List[str]] = None,
+ new_files: Optional[List[str]] = None,
+ new_archives: Optional[List[str]] = None,
+ new_configs: Optional[Dict[str, str]] = None,
+ ):
+ super().__init__(
+ _type="spark",
+ _new_executable=new_executable,
+ _new_arguments=new_arguments,
+ _new_environments=new_environments,
+ _new_custom_fields=new_custom_fields,
+ )
+ self._new_class_name = new_class_name
+ self._new_jars = new_jars
+ self._new_files = new_files
+ self._new_archives = new_archives
+ self._new_configs = new_configs
+
+ def to_template_update(self) -> TemplateUpdate:
+ return SparkTemplateUpdate(
+ new_executable=self._new_executable,
+ new_arguments=self._new_arguments,
+ new_environments=self._new_environments,
+ new_custom_fields=self._new_custom_fields,
+ new_class_name=self._new_class_name,
+ new_jars=self._new_jars,
+ new_files=self._new_files,
+ new_archives=self._new_archives,
+ new_configs=self._new_configs,
+ )
diff --git a/clients/client-python/gravitino/dto/job/template_update_dto.py
b/clients/client-python/gravitino/dto/job/template_update_dto.py
new file mode 100644
index 0000000000..9e12ba70bc
--- /dev/null
+++ b/clients/client-python/gravitino/dto/job/template_update_dto.py
@@ -0,0 +1,44 @@
+# 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 abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from typing import Optional, List, Dict
+
+from dataclasses_json import config, DataClassJsonMixin
+
+from gravitino.api.job.job_template_change import TemplateUpdate
+
+
+@dataclass
+class TemplateUpdateDTO(DataClassJsonMixin, ABC):
+ """Represents a template update data transfer object (DTO)."""
+
+ _type: str = field(metadata=config(field_name="@type"))
+ _new_executable: Optional[str] =
field(metadata=config(field_name="newExecutable"))
+ _new_arguments: Optional[List[str]] = field(
+ metadata=config(field_name="newArguments")
+ )
+ _new_environments: Optional[Dict[str, str]] = field(
+ metadata=config(field_name="newEnvironments")
+ )
+ _new_custom_fields: Optional[Dict[str, str]] = field(
+ metadata=config(field_name="newCustomFields")
+ )
+
+ @abstractmethod
+ def to_template_update(self) -> TemplateUpdate:
+ pass
diff --git
a/clients/client-python/gravitino/dto/requests/job_template_update_request.py
b/clients/client-python/gravitino/dto/requests/job_template_update_request.py
new file mode 100644
index 0000000000..32f0ca0f00
--- /dev/null
+++
b/clients/client-python/gravitino/dto/requests/job_template_update_request.py
@@ -0,0 +1,113 @@
+# 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 abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+
+from dataclasses_json import config
+
+from gravitino.api.job.job_template_change import JobTemplateChange
+from gravitino.dto.job.template_update_dto import TemplateUpdateDTO
+from gravitino.rest.rest_message import RESTRequest
+
+
+@dataclass
+class JobTemplateUpdateRequest(RESTRequest, ABC):
+ """Represents a request to update a job template."""
+
+ _type: str = field(metadata=config(field_name="@type"))
+
+ def __init__(self, action_type: str):
+ self._type = action_type
+
+ @abstractmethod
+ def job_template_change(self) -> JobTemplateChange:
+ """Converts the request to a JobTemplateChange object."""
+ pass
+
+
+@dataclass
+class RenameJobTemplateRequest(JobTemplateUpdateRequest):
+ """Request to rename a job template."""
+
+ _new_name: str = field(metadata=config(field_name="newName"))
+ """The new name for the job template."""
+
+ def __init__(self, new_name: str):
+ super().__init__("rename")
+ self._new_name = new_name
+
+ def validate(self):
+ """Validates the fields of the request.
+
+ Raises:
+ ValueError if the new name is not set.
+ """
+ if not self._new_name:
+ raise ValueError('"new_name" field is required and cannot be
empty')
+
+ def job_template_change(self) -> JobTemplateChange:
+ return JobTemplateChange.rename(new_name=self._new_name)
+
+
+@dataclass
+class UpdateJobTemplateCommentRequest(JobTemplateUpdateRequest):
+ """Request to update the comment of a job template."""
+
+ _new_comment: str = field(metadata=config(field_name="newComment"))
+ """The new comment for the job template."""
+
+ def __init__(self, new_comment: str):
+ super().__init__("updateComment")
+ self._new_comment = new_comment
+
+ def validate(self):
+ """Validates the fields of the request.
+
+ Raises:
+ ValueError if the new comment is not set.
+ """
+ if self._new_comment is None:
+ raise ValueError('"new_comment" field is required and cannot be
None')
+
+ def job_template_change(self) -> JobTemplateChange:
+ return JobTemplateChange.update_comment(new_comment=self._new_comment)
+
+
+@dataclass
+class UpdateJobTemplateContentRequest(JobTemplateUpdateRequest):
+ """Request to update the content of a job template."""
+
+ _new_template: TemplateUpdateDTO =
field(metadata=config(field_name="newTemplate"))
+ """The template update details."""
+
+ def __init__(self, new_template: TemplateUpdateDTO):
+ super().__init__("updateTemplate")
+ self._new_template = new_template
+
+ def validate(self):
+ """Validates the fields of the request.
+
+ Raises:
+ ValueError if the template update is not set.
+ """
+ if self._new_template is None:
+ raise ValueError('"new_template" field is required and cannot be
None')
+
+ def job_template_change(self) -> JobTemplateChange:
+ return JobTemplateChange.update_template(
+ self._new_template.to_template_update()
+ )
diff --git
a/clients/client-python/gravitino/dto/requests/job_template_updates_request.py
b/clients/client-python/gravitino/dto/requests/job_template_updates_request.py
new file mode 100644
index 0000000000..a1fd2383c7
--- /dev/null
+++
b/clients/client-python/gravitino/dto/requests/job_template_updates_request.py
@@ -0,0 +1,38 @@
+# 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 dataclasses import field, dataclass
+from typing import List
+
+from dataclasses_json import config
+
+from gravitino.dto.requests.job_template_update_request import
JobTemplateUpdateRequest
+from gravitino.rest.rest_message import RESTRequest
+
+
+@dataclass
+class JobTemplateUpdatesRequest(RESTRequest):
+ """Represents a request to get job template updates."""
+
+ _updates: List[JobTemplateUpdateRequest] = field(
+ metadata=config(field_name="updates"), default_factory=list
+ )
+
+ def validate(self):
+ if not self._updates:
+ raise ValueError("Updates cannot be empty")
+ for update_request in self._updates:
+ update_request.validate()
diff --git a/clients/client-python/tests/unittests/test_supports_jobs.py
b/clients/client-python/tests/unittests/test_supports_jobs.py
index 697c2a54b7..a24766d714 100644
--- a/clients/client-python/tests/unittests/test_supports_jobs.py
+++ b/clients/client-python/tests/unittests/test_supports_jobs.py
@@ -21,6 +21,7 @@ from unittest.mock import Mock, patch
from gravitino import GravitinoClient
from gravitino.api.job.job_handle import JobHandle
from gravitino.api.job.job_template import JobType
+from gravitino.api.job.job_template_change import JobTemplateChange,
ShellTemplateUpdate
from gravitino.api.job.shell_job_template import ShellJobTemplate
from gravitino.api.job.spark_job_template import SparkJobTemplate
from gravitino.dto.audit_dto import AuditDTO
@@ -133,6 +134,31 @@ class TestSupportsJobs(unittest.TestCase):
result = gravitino_client.delete_job_template(shell_template.name)
self.assertFalse(result)
+ def test_alter_job_template(self, *mock_methods):
+ gravitino_client = GravitinoClient(
+ uri="http://localhost:8090",
+ metalake_name=self._metalake_name,
+ )
+
+ shell_template = self._new_shell_job_template()
+ shell_template_dto = self._new_shell_job_template_dto(shell_template)
+ resp = JobTemplateResponse(_job_template=shell_template_dto, _code=0)
+ mock_resp = self._mock_http_response(resp.to_json())
+
+ with patch(
+ "gravitino.utils.http_client.HTTPClient.put",
return_value=mock_resp
+ ):
+ result_template = gravitino_client.alter_job_template(
+ shell_template.name,
+ JobTemplateChange.rename(shell_template.name),
+ JobTemplateChange.update_comment(shell_template.comment),
+ JobTemplateChange.update_template(
+ ShellTemplateUpdate(new_executable="test")
+ ),
+ )
+
+ self.assertEqual(shell_template, result_template)
+
def test_list_jobs(self, *mock_methods):
gravitino_client = GravitinoClient(
uri="http://localhost:8090",