This is an automated email from the ASF dual-hosted git repository.

jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new f99d3c9815 [#7380] feat(model): Add multiple URIs APIs for model 
version  (#7386)
f99d3c9815 is described below

commit f99d3c9815d682a9ed8a2aacc552d1d6002fd589
Author: XiaoZ <[email protected]>
AuthorDate: Tue Jun 24 20:02:49 2025 +0800

    [#7380] feat(model): Add multiple URIs APIs for model version  (#7386)
    
    ### What changes were proposed in this pull request?
    
    Add multiple URIs APIs for model version.
    
    ### Why are the changes needed?
    
    Fix: #7380
    
    ### Does this PR introduce _any_ user-facing change?
    
    No, not implemented yet.
    
    ### How was this patch tested?
    
    Existing tests.
    
    ---------
    
    Co-authored-by: zhanghan <[email protected]>
---
 .../NoSuchModelVersionURINameException.java        |  48 ++++++++++
 .../org/apache/gravitino/model/ModelCatalog.java   |  65 +++++++++++++-
 .../org/apache/gravitino/model/ModelVersion.java   |  27 +++++-
 .../fileset/TestFilesetCatalogOperations.java      |  12 +++
 .../gravitino/catalog/hive/TestHiveCatalog.java    |   6 ++
 .../catalog/kafka/TestKafkaCatalogOperations.java  |   6 ++
 .../lakehouse/hudi/TestHudiCatalogOperations.java  |   6 ++
 .../lakehouse/iceberg/TestIcebergCatalog.java      |   6 ++
 .../lakehouse/paimon/TestPaimonCatalog.java        |   6 ++
 .../gravitino/catalog/model/ModelCatalogImpl.java  |   8 ++
 .../catalog/model/ModelPropertiesMetadata.java     |  17 +++-
 ...ta.java => ModelVersionPropertiesMetadata.java} |  19 +++-
 .../gravitino/api/model/model_version.py           |  23 ++++-
 .../gravitino/client/generic_model_catalog.py      | 100 +++++++++++++++++++++
 .../gravitino/client/generic_model_version.py      |   3 +
 .../gravitino/dto/model_version_dto.py             |   3 +
 clients/client-python/gravitino/exceptions/base.py |   4 +
 .../exceptions/handlers/model_error_handler.py     |   3 +
 .../catalog/ModelOperationDispatcher.java          |  27 ++++--
 .../apache/gravitino/connector/BaseCatalog.java    |   6 ++
 .../gravitino/connector/HasPropertyMetadata.java   |   8 ++
 .../java/org/apache/gravitino/TestCatalog.java     |   5 ++
 22 files changed, 387 insertions(+), 21 deletions(-)

diff --git 
a/api/src/main/java/org/apache/gravitino/exceptions/NoSuchModelVersionURINameException.java
 
b/api/src/main/java/org/apache/gravitino/exceptions/NoSuchModelVersionURINameException.java
new file mode 100644
index 0000000000..2d7eadeeff
--- /dev/null
+++ 
b/api/src/main/java/org/apache/gravitino/exceptions/NoSuchModelVersionURINameException.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+package org.apache.gravitino.exceptions;
+
+import com.google.errorprone.annotations.FormatMethod;
+
+/** Exception thrown when a URI name of model version is not found. */
+public class NoSuchModelVersionURINameException extends NotFoundException {
+
+  /**
+   * Constructs a new exception with the specified detail message.
+   *
+   * @param message the detail message.
+   * @param args the arguments to the message.
+   */
+  @FormatMethod
+  public NoSuchModelVersionURINameException(String message, Object... args) {
+    super(message, args);
+  }
+
+  /**
+   * Constructs a new exception with the specified detail message and cause.
+   *
+   * @param cause the cause.
+   * @param message the detail message.
+   * @param args the arguments to the message.
+   */
+  @FormatMethod
+  public NoSuchModelVersionURINameException(Throwable cause, String message, 
Object... args) {
+    super(cause, message, args);
+  }
+}
diff --git a/api/src/main/java/org/apache/gravitino/model/ModelCatalog.java 
b/api/src/main/java/org/apache/gravitino/model/ModelCatalog.java
index a2253fdcbe..9ef85ea42b 100644
--- a/api/src/main/java/org/apache/gravitino/model/ModelCatalog.java
+++ b/api/src/main/java/org/apache/gravitino/model/ModelCatalog.java
@@ -18,6 +18,9 @@
  */
 package org.apache.gravitino.model;
 
+import static org.apache.gravitino.model.ModelVersion.URI_NAME_UNKNOWN;
+
+import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
@@ -26,6 +29,7 @@ import 
org.apache.gravitino.exceptions.ModelAlreadyExistsException;
 import 
org.apache.gravitino.exceptions.ModelVersionAliasesAlreadyExistException;
 import org.apache.gravitino.exceptions.NoSuchModelException;
 import org.apache.gravitino.exceptions.NoSuchModelVersionException;
+import org.apache.gravitino.exceptions.NoSuchModelVersionURINameException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 
 /**
@@ -219,13 +223,70 @@ public interface ModelCatalog {
    * @throws NoSuchModelException If the model does not exist.
    * @throws ModelVersionAliasesAlreadyExistException If the aliases already 
exist in the model.
    */
-  void linkModelVersion(
+  default void linkModelVersion(
       NameIdentifier ident,
       String uri,
       String[] aliases,
       String comment,
       Map<String, String> properties)
-      throws NoSuchModelException, ModelVersionAliasesAlreadyExistException;
+      throws NoSuchModelException, ModelVersionAliasesAlreadyExistException {
+    linkModelVersion(ident, ImmutableMap.of(URI_NAME_UNKNOWN, uri), aliases, 
comment, properties);
+  }
+
+  /**
+   * Link a new model version to the registered model object. The new model 
version will be added to
+   * the model object. If the model object does not exist, it will throw an 
exception. If the
+   * version alias already exists in the model, it will throw an exception.
+   *
+   * @param ident The name identifier of the model.
+   * @param uris The names and URIs of the model version artifact.
+   * @param aliases The aliases of the model version. The aliases should be 
unique in this model,
+   *     otherwise the {@link ModelVersionAliasesAlreadyExistException} will 
be thrown. The aliases
+   *     are optional and can be empty. Also, be aware that the alias cannot 
be a number or a number
+   *     string.
+   * @param comment The comment of the model version. The comment is optional 
and can be null.
+   * @param properties The properties of the model version. The properties are 
optional and can be
+   *     null or empty.
+   * @throws NoSuchModelException If the model does not exist.
+   * @throws ModelVersionAliasesAlreadyExistException If the aliases already 
exist in the model.
+   */
+  default void linkModelVersion(
+      NameIdentifier ident,
+      Map<String, String> uris,
+      String[] aliases,
+      String comment,
+      Map<String, String> properties)
+      throws NoSuchModelException, ModelVersionAliasesAlreadyExistException {
+    throw new UnsupportedOperationException("Not supported yet");
+  }
+
+  /**
+   * Get the URI of the model artifact with a specified version number and URI 
name.
+   *
+   * @param ident The name identifier of the model.
+   * @param version The version number of the model.
+   * @param uriName The name of the URI. If null, the default URI will be used.
+   * @throws NoSuchModelVersionException If the model version does not exist.
+   * @return The URI of the model version.
+   */
+  default String getModelVersionUri(NameIdentifier ident, int version, String 
uriName)
+      throws NoSuchModelVersionException, NoSuchModelVersionURINameException {
+    throw new UnsupportedOperationException("Not supported yet");
+  }
+
+  /**
+   * Get the URI of the model artifact with a specified version alias and URI 
name.
+   *
+   * @param ident The name identifier of the model.
+   * @param alias The version alias of the model.
+   * @param uriName The name of the URI. If null, the default URI will be used.
+   * @throws NoSuchModelVersionException If the model version does not exist.
+   * @return The URI of the model version.
+   */
+  default String getModelVersionUri(NameIdentifier ident, String alias, String 
uriName)
+      throws NoSuchModelVersionException, NoSuchModelVersionURINameException {
+    throw new UnsupportedOperationException("Not supported yet");
+  }
 
   /**
    * Delete the model version by the {@link NameIdentifier} and version 
number. If the model version
diff --git a/api/src/main/java/org/apache/gravitino/model/ModelVersion.java 
b/api/src/main/java/org/apache/gravitino/model/ModelVersion.java
index 2557c86077..5f6fa5bf42 100644
--- a/api/src/main/java/org/apache/gravitino/model/ModelVersion.java
+++ b/api/src/main/java/org/apache/gravitino/model/ModelVersion.java
@@ -31,6 +31,12 @@ import org.apache.gravitino.annotation.Evolving;
 @Evolving
 public interface ModelVersion extends Auditable {
 
+  /** The property name for the default uri name of the model version. */
+  String PROPERTY_DEFAULT_URI_NAME = "default-uri-name";
+
+  /** The reserved URI name to indicate the URI name is unknown. */
+  String URI_NAME_UNKNOWN = "unknown";
+
   /**
    * The version of this model object. The version number is an integer number 
starts from 0. Each
    * time the model checkpoint / snapshot is linked to the registered, the 
version number will be
@@ -60,12 +66,27 @@ public interface ModelVersion extends Auditable {
   String[] aliases();
 
   /**
-   * The URI of the model artifact. The URI is the location of the model 
artifact. The URI can be a
-   * file path or a remote URI.
+   * The unnamed URI of the model artifact. The URI is the location of the 
model artifact. The URI
+   * can be a file path or a remote URI.
    *
    * @return The URI of the model artifact.
    */
-  String uri();
+  default String uri() {
+    return uris().get(URI_NAME_UNKNOWN);
+  }
+
+  /**
+   * The name and corresponding URI of the model artifact. The key is the name 
of the URI, and the
+   * value is the URI of the model artifact, which can be a file path or a 
remote URI.
+   *
+   * <p>The "unknown" URI name is reserved for the compatibility with single 
URI.
+   *
+   * @return The URIs of the model version, the key is the name of the URI and 
the value is the URI
+   *     of the model artifact.
+   */
+  default Map<String, String> uris() {
+    throw new UnsupportedOperationException("Not implemented yet");
+  }
 
   /**
    * The properties of the model version. The properties are key-value pairs 
that can be used to
diff --git 
a/catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCatalogOperations.java
 
b/catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCatalogOperations.java
index e3d7a95ca7..8c4fd4cdd8 100644
--- 
a/catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCatalogOperations.java
+++ 
b/catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCatalogOperations.java
@@ -151,6 +151,12 @@ public class TestFilesetCatalogOperations {
         public PropertiesMetadata modelPropertiesMetadata() throws 
UnsupportedOperationException {
           throw new UnsupportedOperationException("Does not support model 
properties");
         }
+
+        @Override
+        public PropertiesMetadata modelVersionPropertiesMetadata()
+            throws UnsupportedOperationException {
+          throw new UnsupportedOperationException("Does not support model 
version properties");
+        }
       };
 
   private static EntityStore store;
@@ -1184,6 +1190,12 @@ public class TestFilesetCatalogOperations {
           public PropertiesMetadata modelPropertiesMetadata() throws 
UnsupportedOperationException {
             return null;
           }
+
+          @Override
+          public PropertiesMetadata modelVersionPropertiesMetadata()
+              throws UnsupportedOperationException {
+            return null;
+          }
         };
 
     try {
diff --git 
a/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalog.java
 
b/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalog.java
index 910fe2db70..9528d9b2fb 100644
--- 
a/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalog.java
+++ 
b/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalog.java
@@ -71,6 +71,12 @@ public class TestHiveCatalog extends 
MiniHiveMetastoreService {
         public PropertiesMetadata modelPropertiesMetadata() throws 
UnsupportedOperationException {
           throw new UnsupportedOperationException("Model properties are not 
supported");
         }
+
+        @Override
+        public PropertiesMetadata modelVersionPropertiesMetadata()
+            throws UnsupportedOperationException {
+          throw new UnsupportedOperationException("Does not support model 
version properties");
+        }
       };
 
   @Test
diff --git 
a/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/TestKafkaCatalogOperations.java
 
b/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/TestKafkaCatalogOperations.java
index 007477271d..ef90c81893 100644
--- 
a/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/TestKafkaCatalogOperations.java
+++ 
b/catalogs/catalog-kafka/src/test/java/org/apache/gravitino/catalog/kafka/TestKafkaCatalogOperations.java
@@ -121,6 +121,12 @@ public class TestKafkaCatalogOperations extends 
KafkaClusterEmbedded {
         public PropertiesMetadata modelPropertiesMetadata() throws 
UnsupportedOperationException {
           throw new UnsupportedOperationException("Not supported");
         }
+
+        @Override
+        public PropertiesMetadata modelVersionPropertiesMetadata()
+            throws UnsupportedOperationException {
+          throw new UnsupportedOperationException("Does not support model 
version properties");
+        }
       };
   private static EntityStore store;
   private static IdGenerator idGenerator;
diff --git 
a/catalogs/catalog-lakehouse-hudi/src/test/java/org/apache/gravitino/catalog/lakehouse/hudi/TestHudiCatalogOperations.java
 
b/catalogs/catalog-lakehouse-hudi/src/test/java/org/apache/gravitino/catalog/lakehouse/hudi/TestHudiCatalogOperations.java
index a4b86c0c5d..8fb4128b88 100644
--- 
a/catalogs/catalog-lakehouse-hudi/src/test/java/org/apache/gravitino/catalog/lakehouse/hudi/TestHudiCatalogOperations.java
+++ 
b/catalogs/catalog-lakehouse-hudi/src/test/java/org/apache/gravitino/catalog/lakehouse/hudi/TestHudiCatalogOperations.java
@@ -64,6 +64,12 @@ public class TestHudiCatalogOperations {
         public PropertiesMetadata modelPropertiesMetadata() throws 
UnsupportedOperationException {
           throw new UnsupportedOperationException();
         }
+
+        @Override
+        public PropertiesMetadata modelVersionPropertiesMetadata()
+            throws UnsupportedOperationException {
+          throw new UnsupportedOperationException("Does not support model 
version properties");
+        }
       };
 
   @Test
diff --git 
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalog.java
 
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalog.java
index 8ff70d3985..acd3c3a1f0 100644
--- 
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalog.java
+++ 
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalog.java
@@ -69,6 +69,12 @@ public class TestIcebergCatalog {
         public PropertiesMetadata modelPropertiesMetadata() throws 
UnsupportedOperationException {
           throw new UnsupportedOperationException("Model properties are not 
supported");
         }
+
+        @Override
+        public PropertiesMetadata modelVersionPropertiesMetadata()
+            throws UnsupportedOperationException {
+          throw new UnsupportedOperationException("Does not support model 
version properties");
+        }
       };
 
   @Test
diff --git 
a/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalog.java
 
b/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalog.java
index 010b6daeef..f7187258f4 100644
--- 
a/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalog.java
+++ 
b/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalog.java
@@ -77,6 +77,12 @@ public class TestPaimonCatalog {
         public PropertiesMetadata modelPropertiesMetadata() throws 
UnsupportedOperationException {
           throw new UnsupportedOperationException("Model properties are not 
supported");
         }
+
+        @Override
+        public PropertiesMetadata modelVersionPropertiesMetadata()
+            throws UnsupportedOperationException {
+          throw new UnsupportedOperationException("Does not support model 
version properties");
+        }
       };
 
   private static String tempDir =
diff --git 
a/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogImpl.java
 
b/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogImpl.java
index 545f6482a3..ba3bfa4984 100644
--- 
a/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogImpl.java
+++ 
b/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogImpl.java
@@ -37,6 +37,9 @@ public class ModelCatalogImpl extends 
BaseCatalog<ModelCatalogImpl> {
   private static final ModelPropertiesMetadata MODEL_PROPERTIES_META =
       new ModelPropertiesMetadata();
 
+  private static final ModelVersionPropertiesMetadata 
MODEL_VERSION_PROPERTIES_META =
+      new ModelVersionPropertiesMetadata();
+
   @Override
   public String shortName() {
     return "model";
@@ -63,6 +66,11 @@ public class ModelCatalogImpl extends 
BaseCatalog<ModelCatalogImpl> {
     return MODEL_PROPERTIES_META;
   }
 
+  @Override
+  public PropertiesMetadata modelVersionPropertiesMetadata() throws 
UnsupportedOperationException {
+    return MODEL_VERSION_PROPERTIES_META;
+  }
+
   @Override
   protected Capability newCapability() {
     return new ModelCatalogCapability();
diff --git 
a/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelPropertiesMetadata.java
 
b/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelPropertiesMetadata.java
index 67bd58e76f..0ae316f0b7 100644
--- 
a/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelPropertiesMetadata.java
+++ 
b/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelPropertiesMetadata.java
@@ -18,15 +18,28 @@
  */
 package org.apache.gravitino.catalog.model;
 
-import java.util.Collections;
+import static 
org.apache.gravitino.model.ModelVersion.PROPERTY_DEFAULT_URI_NAME;
+
+import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import org.apache.gravitino.connector.BasePropertiesMetadata;
 import org.apache.gravitino.connector.PropertyEntry;
 
 public class ModelPropertiesMetadata extends BasePropertiesMetadata {
+  private static final Map<String, PropertyEntry<?>> MODEL_PROPERTY_ENTRIES =
+      ImmutableMap.<String, PropertyEntry<?>>builder()
+          .put(
+              PROPERTY_DEFAULT_URI_NAME,
+              PropertyEntry.stringOptionalPropertyEntry(
+                  PROPERTY_DEFAULT_URI_NAME,
+                  "The default URI name for the versions of the model",
+                  false /* immutable */,
+                  null,
+                  false /* hidden */))
+          .build();
 
   @Override
   protected Map<String, PropertyEntry<?>> specificPropertyEntries() {
-    return Collections.emptyMap();
+    return MODEL_PROPERTY_ENTRIES;
   }
 }
diff --git 
a/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelPropertiesMetadata.java
 
b/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelVersionPropertiesMetadata.java
similarity index 59%
copy from 
catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelPropertiesMetadata.java
copy to 
catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelVersionPropertiesMetadata.java
index 67bd58e76f..ec31af59e3 100644
--- 
a/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelPropertiesMetadata.java
+++ 
b/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelVersionPropertiesMetadata.java
@@ -18,15 +18,28 @@
  */
 package org.apache.gravitino.catalog.model;
 
-import java.util.Collections;
+import static 
org.apache.gravitino.model.ModelVersion.PROPERTY_DEFAULT_URI_NAME;
+
+import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import org.apache.gravitino.connector.BasePropertiesMetadata;
 import org.apache.gravitino.connector.PropertyEntry;
 
-public class ModelPropertiesMetadata extends BasePropertiesMetadata {
+public class ModelVersionPropertiesMetadata extends BasePropertiesMetadata {
+  private static final Map<String, PropertyEntry<?>> 
MODEL_VERSION_PROPERTY_ENTRIES =
+      ImmutableMap.<String, PropertyEntry<?>>builder()
+          .put(
+              PROPERTY_DEFAULT_URI_NAME,
+              PropertyEntry.stringOptionalPropertyEntry(
+                  PROPERTY_DEFAULT_URI_NAME,
+                  "The default URI name for the model version",
+                  false /* immutable */,
+                  null,
+                  false /* hidden */))
+          .build();
 
   @Override
   protected Map<String, PropertyEntry<?>> specificPropertyEntries() {
-    return Collections.emptyMap();
+    return MODEL_VERSION_PROPERTY_ENTRIES;
   }
 }
diff --git a/clients/client-python/gravitino/api/model/model_version.py 
b/clients/client-python/gravitino/api/model/model_version.py
index cdf8f05bd5..f1737cd2e9 100644
--- a/clients/client-python/gravitino/api/model/model_version.py
+++ b/clients/client-python/gravitino/api/model/model_version.py
@@ -28,6 +28,12 @@ class ModelVersion(Auditable):
     registered model.
     """
 
+    PROPERTY_DEFAULT_URI_NAME = "default-uri-name"
+    """The property name for the default URI name of the model version."""
+
+    URI_NAME_UNKNOWN = "unknown"
+    """The reserved URI name to indicate the URI name is unknown."""
+
     @abstractmethod
     def version(self) -> int:
         """
@@ -63,15 +69,28 @@ class ModelVersion(Auditable):
         """
         pass
 
-    @abstractmethod
     def uri(self) -> str:
         """
-        The URI of the model artifact. The URI is the location of the model 
artifact. The URI can be a
+        The unnamed URI of the model artifact. The URI is the location of the 
model artifact. The URI can be a
         file path or a remote URI.
 
         Returns:
             The URI of the model artifact.
         """
+        return self.uris().get(self.URI_NAME_UNKNOWN)
+
+    @abstractmethod
+    def uris(self) -> Dict[str, str]:
+        """
+        The name and corresponding URI of the model artifact. The key is the 
name of the URI, and the
+        value is the location of the model artifact, which can be a file path 
or a remote URI.
+
+        The "unknown" URI name is reserved for the compatibility with single 
URI.
+
+        Returns:
+            The URIs of the model version, the key is the name of the URI and 
the value is the URI of
+            the model artifact.
+        """
         pass
 
     def properties(self) -> Dict[str, str]:
diff --git a/clients/client-python/gravitino/client/generic_model_catalog.py 
b/clients/client-python/gravitino/client/generic_model_catalog.py
index 34074284dc..0d2d4c8f39 100644
--- a/clients/client-python/gravitino/client/generic_model_catalog.py
+++ b/clients/client-python/gravitino/client/generic_model_catalog.py
@@ -465,6 +465,72 @@ class GenericModelCatalog(BaseSchemaCatalog):
         base_resp = BaseResponse.from_json(resp.body, infer_missing=True)
         base_resp.validate()
 
+    def link_model_version_with_multiple_uris(
+        self,
+        model_ident: NameIdentifier,
+        uris: Dict[str, str],
+        aliases: List[str],
+        comment: str,
+        properties: Dict[str, str],
+    ) -> None:
+        """Link a new model version to the registered model object. The new 
model version will be
+        added to the model object. If the model object does not exist, it will 
throw an
+        exception. If the version alias already exists in the model, it will 
throw an exception.
+
+        Args:
+            model_ident: The identifier of the model.
+            uris: The URIs and their names of the model version.
+            aliases: The aliases of the model version. The aliases of the 
model version. The
+            aliases should be unique in this model, otherwise the
+            ModelVersionAliasesAlreadyExistException will be thrown. The 
aliases are optional and
+            can be empty.
+            comment: The comment of the model version.
+            properties: The properties of the model version.
+
+        Raises:
+            NoSuchModelException: If the model does not exist.
+            ModelVersionAliasesAlreadyExistException: If the aliases of the 
model version already exist.
+        """
+        raise NotImplementedError("Not supported yet")
+
+    def get_model_version_uri(
+        self, model_ident: NameIdentifier, version: int, uri_name: str = None
+    ):
+        """Get the URI of the model artifact with a specified version number.
+
+        Args:
+            model_ident: The identifier of the model.
+            version: The version of the model.
+            uri_name: The name of the URI. If None, the default URI will be 
used.
+
+        Raises:
+            NoSuchModelVersionException: If the model version does not exist.
+            NoSuchModelVersionURINameException: If the uri name does not exist.
+
+        Returns:
+            The URI of the model version.
+        """
+        raise NotImplementedError("Not supported yet")
+
+    def get_model_version_uri_by_alias(
+        self, model_ident: NameIdentifier, alias: str, uri_name: str = None
+    ):
+        """Get the URI of the model artifact with a specified version alias.
+
+        Args:
+            model_ident: The identifier of the model.
+            alias: The alias of the model version.
+            uri_name: The name of the URI. If None, the default URI will be 
used.
+
+        Raises:
+            NoSuchModelVersionException: If the model version does not exist.
+            NoSuchModelVersionURINameException: If the uri name does not exist.
+
+        Returns:
+            The URI of the model version.
+        """
+        raise NotImplementedError("Not supported yet")
+
     def delete_model_version(self, model_ident: NameIdentifier, version: int) 
-> bool:
         """Delete the model version from the catalog. If the model version 
does not exist, return false.
         If the model version is successfully deleted, return true.
@@ -546,6 +612,40 @@ class GenericModelCatalog(BaseSchemaCatalog):
         self.link_model_version(ident, uri, aliases, comment, properties)
         return model
 
+    def register_model_version_with_multiple_uris(
+        self,
+        ident: NameIdentifier,
+        uris: Dict[str, str],
+        aliases: List[str],
+        comment: str,
+        properties: Dict[str, str],
+    ) -> Model:
+        """Register a model in the catalog if the model is not existed, 
otherwise the
+        ModelAlreadyExistsException will be thrown. The Model object will be 
created when the
+        model is registered, in the meantime, the model version (version 0) 
will also be created and
+        linked to the registered model. Register a model in the catalog and 
link a new model
+        version to the registered model.
+
+        Args:
+            ident: The identifier of the model.
+            uris: The URIs and their names of the model version.
+            aliases: The aliases of the model version.
+            comment: The comment of the model.
+            properties: The properties of the model.
+
+        Raises:
+            ModelAlreadyExistsException: If the model already exists.
+            ModelVersionAliasesAlreadyExistException: If the aliases of the 
model version already exist.
+
+        Returns:
+            The registered model object.
+        """
+        model = self.register_model(ident, comment, properties)
+        self.link_model_version_with_multiple_uris(
+            ident, uris, aliases, comment, properties
+        )
+        return model
+
     @staticmethod
     def to_model_update_request(change: ModelChange):
         if isinstance(change, ModelChange.RenameModel):
diff --git a/clients/client-python/gravitino/client/generic_model_version.py 
b/clients/client-python/gravitino/client/generic_model_version.py
index 3cd5003977..0c13f53668 100644
--- a/clients/client-python/gravitino/client/generic_model_version.py
+++ b/clients/client-python/gravitino/client/generic_model_version.py
@@ -40,6 +40,9 @@ class GenericModelVersion(ModelVersion):
     def uri(self) -> str:
         return self._model_version_dto.uri()
 
+    def uris(self) -> Dict[str, str]:
+        return self._model_version_dto.uris()
+
     def properties(self) -> Dict[str, str]:
         return self._model_version_dto.properties()
 
diff --git a/clients/client-python/gravitino/dto/model_version_dto.py 
b/clients/client-python/gravitino/dto/model_version_dto.py
index 2b287069e7..939eb2e347 100644
--- a/clients/client-python/gravitino/dto/model_version_dto.py
+++ b/clients/client-python/gravitino/dto/model_version_dto.py
@@ -49,6 +49,9 @@ class ModelVersionDTO(ModelVersion, DataClassJsonMixin):
     def uri(self) -> str:
         return self._uri
 
+    def uris(self) -> Dict[str, str]:
+        raise NotImplementedError("Not supported yet")
+
     def properties(self) -> Optional[Dict[str, str]]:
         return self._properties
 
diff --git a/clients/client-python/gravitino/exceptions/base.py 
b/clients/client-python/gravitino/exceptions/base.py
index fed9585659..505655d869 100644
--- a/clients/client-python/gravitino/exceptions/base.py
+++ b/clients/client-python/gravitino/exceptions/base.py
@@ -85,6 +85,10 @@ class NoSuchModelVersionException(NotFoundException):
     """An exception thrown when a model version is not found."""
 
 
+class NoSuchModelVersionURINameException(NotFoundException):
+    """An exception thrown when a URI name of a model version is not found."""
+
+
 class AlreadyExistsException(GravitinoRuntimeException):
     """Base exception thrown when an entity or resource already exists."""
 
diff --git 
a/clients/client-python/gravitino/exceptions/handlers/model_error_handler.py 
b/clients/client-python/gravitino/exceptions/handlers/model_error_handler.py
index eec52d19d5..a611a76725 100644
--- a/clients/client-python/gravitino/exceptions/handlers/model_error_handler.py
+++ b/clients/client-python/gravitino/exceptions/handlers/model_error_handler.py
@@ -20,6 +20,7 @@ from gravitino.exceptions.base import (
     NoSuchSchemaException,
     NoSuchModelException,
     NoSuchModelVersionException,
+    NoSuchModelVersionURINameException,
     NotFoundException,
     ModelAlreadyExistsException,
     ModelVersionAliasesAlreadyExistException,
@@ -44,6 +45,8 @@ class ModelErrorHandler(RestErrorHandler):
                 raise NoSuchModelException(error_message)
             if exception_type == NoSuchModelVersionException.__name__:
                 raise NoSuchModelVersionException(error_message)
+            if exception_type == NoSuchModelVersionURINameException.__name__:
+                raise NoSuchModelVersionURINameException(error_message)
 
             raise NotFoundException(error_message)
 
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/ModelOperationDispatcher.java 
b/core/src/main/java/org/apache/gravitino/catalog/ModelOperationDispatcher.java
index 4f62c824de..b2de2b20c1 100644
--- 
a/core/src/main/java/org/apache/gravitino/catalog/ModelOperationDispatcher.java
+++ 
b/core/src/main/java/org/apache/gravitino/catalog/ModelOperationDispatcher.java
@@ -23,12 +23,14 @@ import static 
org.apache.gravitino.utils.NameIdentifierUtil.getCatalogIdentifier
 
 import java.util.Arrays;
 import java.util.Map;
+import java.util.function.Function;
 import java.util.function.Supplier;
 import org.apache.gravitino.EntityStore;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.StringIdentifier;
 import org.apache.gravitino.connector.HasPropertyMetadata;
+import org.apache.gravitino.connector.PropertiesMetadata;
 import org.apache.gravitino.exceptions.ModelAlreadyExistsException;
 import 
org.apache.gravitino.exceptions.ModelVersionAliasesAlreadyExistException;
 import org.apache.gravitino.exceptions.NoSuchModelException;
@@ -86,7 +88,9 @@ public class ModelOperationDispatcher extends 
OperationDispatcher implements Mod
   public Model registerModel(NameIdentifier ident, String comment, Map<String, 
String> properties)
       throws NoSuchModelException, ModelAlreadyExistsException {
     NameIdentifier catalogIdent = getCatalogIdentifier(ident);
-    Map<String, String> updatedProperties = 
checkAndUpdateProperties(catalogIdent, properties);
+    Map<String, String> updatedProperties =
+        checkAndUpdateProperties(
+            catalogIdent, properties, 
HasPropertyMetadata::modelPropertiesMetadata);
 
     Model registeredModel =
         TreeLockUtils.doWithTreeLock(
@@ -187,7 +191,9 @@ public class ModelOperationDispatcher extends 
OperationDispatcher implements Mod
       Map<String, String> properties)
       throws NoSuchModelException, ModelVersionAliasesAlreadyExistException {
     NameIdentifier catalogIdent = getCatalogIdentifier(ident);
-    Map<String, String> updatedProperties = 
checkAndUpdateProperties(catalogIdent, properties);
+    Map<String, String> updatedProperties =
+        checkAndUpdateProperties(
+            catalogIdent, properties, 
HasPropertyMetadata::modelVersionPropertiesMetadata);
 
     TreeLockUtils.doWithTreeLock(
         ident,
@@ -260,7 +266,7 @@ public class ModelOperationDispatcher extends 
OperationDispatcher implements Mod
   public ModelVersion alterModelVersion(
       NameIdentifier ident, int version, ModelVersionChange... changes)
       throws NoSuchModelVersionException, IllegalArgumentException {
-    validateAlterProperties(ident, 
HasPropertyMetadata::modelPropertiesMetadata, changes);
+    validateAlterProperties(ident, 
HasPropertyMetadata::modelVersionPropertiesMetadata, changes);
     return executeAlterModelVersion(ident, f -> f.alterModelVersion(ident, 
version, changes));
   }
 
@@ -269,7 +275,7 @@ public class ModelOperationDispatcher extends 
OperationDispatcher implements Mod
   public ModelVersion alterModelVersion(
       NameIdentifier ident, String alias, ModelVersionChange... changes)
       throws NoSuchModelException, IllegalArgumentException {
-    validateAlterProperties(ident, 
HasPropertyMetadata::modelPropertiesMetadata, changes);
+    validateAlterProperties(ident, 
HasPropertyMetadata::modelVersionPropertiesMetadata, changes);
     return executeAlterModelVersion(ident, f -> f.alterModelVersion(ident, 
alias, changes));
   }
 
@@ -292,7 +298,7 @@ public class ModelOperationDispatcher extends 
OperationDispatcher implements Mod
         .withHiddenProperties(
             getHiddenPropertyNames(
                 catalogIdent,
-                HasPropertyMetadata::modelPropertiesMetadata,
+                HasPropertyMetadata::modelVersionPropertiesMetadata,
                 alteredModelVersion.properties()));
   }
 
@@ -305,7 +311,7 @@ public class ModelOperationDispatcher extends 
OperationDispatcher implements Mod
         .withHiddenProperties(
             getHiddenPropertyNames(
                 catalogIdent,
-                HasPropertyMetadata::modelPropertiesMetadata,
+                HasPropertyMetadata::modelVersionPropertiesMetadata,
                 modelVersion.properties()));
   }
 
@@ -320,13 +326,15 @@ public class ModelOperationDispatcher extends 
OperationDispatcher implements Mod
                     .withHiddenProperties(
                         getHiddenPropertyNames(
                             catalogIdent,
-                            HasPropertyMetadata::modelPropertiesMetadata,
+                            
HasPropertyMetadata::modelVersionPropertiesMetadata,
                             v.properties())))
         .toArray(ModelVersion[]::new);
   }
 
   private Map<String, String> checkAndUpdateProperties(
-      NameIdentifier catalogIdent, Map<String, String> properties) {
+      NameIdentifier catalogIdent,
+      Map<String, String> properties,
+      Function<HasPropertyMetadata, PropertiesMetadata> 
propertiesMetadataProvider) {
     TreeLockUtils.doWithTreeLock(
         catalogIdent,
         LockType.READ,
@@ -336,7 +344,8 @@ public class ModelOperationDispatcher extends 
OperationDispatcher implements Mod
                 c ->
                     c.doWithPropertiesMeta(
                         p -> {
-                          
validatePropertyForCreate(p.modelPropertiesMetadata(), properties);
+                          validatePropertyForCreate(
+                              propertiesMetadataProvider.apply(p), properties);
                           return null;
                         }),
                 IllegalArgumentException.class));
diff --git a/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java 
b/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
index 539a19e331..6c749f903f 100644
--- a/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
+++ b/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
@@ -155,6 +155,12 @@ public abstract class BaseCatalog<T extends BaseCatalog>
         "The catalog does not support model properties metadata");
   }
 
+  @Override
+  public PropertiesMetadata modelVersionPropertiesMetadata() throws 
UnsupportedOperationException {
+    throw new UnsupportedOperationException(
+        "The catalog does not support model version properties metadata");
+  }
+
   /**
    * Retrieves the CatalogOperations instance associated with this catalog. 
Lazily initializes the
    * instance if not already created.
diff --git 
a/core/src/main/java/org/apache/gravitino/connector/HasPropertyMetadata.java 
b/core/src/main/java/org/apache/gravitino/connector/HasPropertyMetadata.java
index e8e02b6ffa..c0fdc8c355 100644
--- a/core/src/main/java/org/apache/gravitino/connector/HasPropertyMetadata.java
+++ b/core/src/main/java/org/apache/gravitino/connector/HasPropertyMetadata.java
@@ -71,4 +71,12 @@ public interface HasPropertyMetadata {
    * @throws UnsupportedOperationException if the entity does not support 
model properties.
    */
   PropertiesMetadata modelPropertiesMetadata() throws 
UnsupportedOperationException;
+
+  /**
+   * Returns the model version property metadata.
+   *
+   * @return The model version property metadata.
+   * @throws UnsupportedOperationException if the entity does not support 
model version properties.
+   */
+  PropertiesMetadata modelVersionPropertiesMetadata() throws 
UnsupportedOperationException;
 }
diff --git a/core/src/test/java/org/apache/gravitino/TestCatalog.java 
b/core/src/test/java/org/apache/gravitino/TestCatalog.java
index fe353b94e9..fbea6b5e59 100644
--- a/core/src/test/java/org/apache/gravitino/TestCatalog.java
+++ b/core/src/test/java/org/apache/gravitino/TestCatalog.java
@@ -191,4 +191,9 @@ public class TestCatalog extends BaseCatalog<TestCatalog> {
   public PropertiesMetadata modelPropertiesMetadata() throws 
UnsupportedOperationException {
     return BASE_PROPERTIES_METADATA;
   }
+
+  @Override
+  public PropertiesMetadata modelVersionPropertiesMetadata() throws 
UnsupportedOperationException {
+    return BASE_PROPERTIES_METADATA;
+  }
 }


Reply via email to