Repository: mlflow/mlflow
Pull request: Feature/implement delete in http artifact repository
<!-- 🚨 We recommend pull requests be filed from a non-master branch on a repository fork (e.g. <username>:fix-xxx). 🚨 -->

## Related Issues/PRs

Closes #5996

## What changes are proposed in this pull request?

Implements the `delete_artifacts` method in the `HttpArtifactRepository`.

## How is this patch tested?

I implemented a unittest for the method.

## Does this PR change the documentation?

- [x] No. You can skip the rest of this section.
- [ ] Yes. Make sure the changed pages / sections render correctly by following the steps below.

1. Check the status of the `ci/circleci: build_doc` check. If it's successful, proceed to the
   next step, otherwise fix it.
2. Click `Details` on the right to open the job page of CircleCI.
3. Click the `Artifacts` tab.
4. Click `docs/build/html/index.html`.
5. Find the changed pages / sections and make sure they render correctly.

## Release Notes

### Is this a user-facing change?

- [ ] No. You can skip the rest of this section.
- [x] Yes. Give a description of this change to be included in the release notes for MLflow users.

Possibility to delete artifacts over http. The HttpArtifactRepository now implements the delete method, which allows users to delete artifacts even when they are served over http.

### What component(s), interfaces, languages, and integrations does this PR affect?
Components
- [x] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [ ] `area/server-infra`: MLflow Tracking server backend
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

Interface
- [ ] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

Language
- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

Integrations
- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations

<!--
Insert an empty named anchor here to allow jumping to this section with a fragment URL
(e.g. https://github.com/mlflow/mlflow/pull/123#user-content-release-note-category).
Note that GitHub prefixes anchor names in markdown with "user-content-".
-->
<a name="release-note-category"></a>
### How should the PR be classified in the release notes? Choose one:

- [ ] `rn/breaking-change` - The PR will be mentioned in the "Breaking Changes" section
- [x] `rn/none` - No description will be included. The PR will be mentioned only by the PR number in the "Small Bugfixes and Documentation Updates" section
- [ ] `rn/feature` - A new user-facing feature worth mentioning in the release notes
- [ ] `rn/bug-fix` - A user-facing bug fix worth mentioning in the release notes
- [ ] `rn/documentation` - A user-facing documentation change worth mentioning in the release notes


Problem statement:
[FR] Delete artifact over HTTP is not implemented
### Willingness to contribute

Yes. I can contribute this feature independently.

### Proposal Summary

The `http_artifact_repository` doesn't implement the `delete_artifacts` method, therefore it's not possible to permanently delete artifacts (for instance with the garbace collector `mlflow gc`).

### Motivation

> #### What is the use case for this feature?

Delete artifacts with e.g. `mlflow gc`.

> #### Why is this use case valuable to support for MLflow users in general?

The abstract `ArtifactRepository` defines a `delete_artifacts` method, which is not implemented in the `HttpArtifactRepository`. Making this endpoint available completes the API.

> #### Why is this use case valuable to support for your project(s) or organization?

We use the `mlflow gc` (experimental) command to clean deleted experiment data. Since we use the `--serve-artifacts` option on the server, the experiments use the `HttpArtifactRepository` to store the models. But because that doesn't have the `delete_artifacts` method implemented, the garbace collector doesn't work.

> #### Why is it currently difficult to achieve this use case?

We had to implement our own `gc` method.

### Details

_No response_

### What component(s) does this bug affect?

- [X] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [ ] `area/server-infra`: MLflow Tracking server backend
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

### What interface(s) does this bug affect?

- [ ] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

### What language(s) does this bug affect?

- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

### What integration(s) does this bug affect?

- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations

Hints from issue discussion:
@idlefella Thanks for the PR, we'll take a look :)

Commit message: Fix pylint errors

Fixed pylint and formatting errors

Signed-off-by: Marc Liechti <marc.liechti@trivo.ch>

Patch to review:
diff --git a/mlflow/java/client/src/main/java/org/mlflow/api/proto/MlflowArtifacts.java b/mlflow/java/client/src/main/java/org/mlflow/api/proto/MlflowArtifacts.java
index 236f843a1fc9a..ac6a5d82cfe9a 100644
--- a/mlflow/java/client/src/main/java/org/mlflow/api/proto/MlflowArtifacts.java
+++ b/mlflow/java/client/src/main/java/org/mlflow/api/proto/MlflowArtifacts.java
@@ -3245,6 +3245,842 @@ public org.mlflow.api.proto.MlflowArtifacts.ListArtifacts getDefaultInstanceForT
 
   }
 
+  public interface DeleteArtifactOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:mlflow.artifacts.DeleteArtifact)
+      com.google.protobuf.MessageOrBuilder {
+  }
+  /**
+   * Protobuf type {@code mlflow.artifacts.DeleteArtifact}
+   */
+  public static final class DeleteArtifact extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:mlflow.artifacts.DeleteArtifact)
+      DeleteArtifactOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use DeleteArtifact.newBuilder() to construct.
+    private DeleteArtifact(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private DeleteArtifact() {
+    }
+
+    @java.lang.Override
+    @SuppressWarnings({"unused"})
+    protected java.lang.Object newInstance(
+        UnusedPrivateParameter unused) {
+      return new DeleteArtifact();
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private DeleteArtifact(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownField(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return org.mlflow.api.proto.MlflowArtifacts.internal_static_mlflow_artifacts_DeleteArtifact_descriptor;
+    }
+
+    @java.lang.Override
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return org.mlflow.api.proto.MlflowArtifacts.internal_static_mlflow_artifacts_DeleteArtifact_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.class, org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Builder.class);
+    }
+
+    public interface ResponseOrBuilder extends
+        // @@protoc_insertion_point(interface_extends:mlflow.artifacts.DeleteArtifact.Response)
+        com.google.protobuf.MessageOrBuilder {
+    }
+    /**
+     * Protobuf type {@code mlflow.artifacts.DeleteArtifact.Response}
+     */
+    public static final class Response extends
+        com.google.protobuf.GeneratedMessageV3 implements
+        // @@protoc_insertion_point(message_implements:mlflow.artifacts.DeleteArtifact.Response)
+        ResponseOrBuilder {
+    private static final long serialVersionUID = 0L;
+      // Use Response.newBuilder() to construct.
+      private Response(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+        super(builder);
+      }
+      private Response() {
+      }
+
+      @java.lang.Override
+      @SuppressWarnings({"unused"})
+      protected java.lang.Object newInstance(
+          UnusedPrivateParameter unused) {
+        return new Response();
+      }
+
+      @java.lang.Override
+      public final com.google.protobuf.UnknownFieldSet
+      getUnknownFields() {
+        return this.unknownFields;
+      }
+      private Response(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        this();
+        if (extensionRegistry == null) {
+          throw new java.lang.NullPointerException();
+        }
+        com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+            com.google.protobuf.UnknownFieldSet.newBuilder();
+        try {
+          boolean done = false;
+          while (!done) {
+            int tag = input.readTag();
+            switch (tag) {
+              case 0:
+                done = true;
+                break;
+              default: {
+                if (!parseUnknownField(
+                    input, unknownFields, extensionRegistry, tag)) {
+                  done = true;
+                }
+                break;
+              }
+            }
+          }
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          throw e.setUnfinishedMessage(this);
+        } catch (java.io.IOException e) {
+          throw new com.google.protobuf.InvalidProtocolBufferException(
+              e).setUnfinishedMessage(this);
+        } finally {
+          this.unknownFields = unknownFields.build();
+          makeExtensionsImmutable();
+        }
+      }
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return org.mlflow.api.proto.MlflowArtifacts.internal_static_mlflow_artifacts_DeleteArtifact_Response_descriptor;
+      }
+
+      @java.lang.Override
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return org.mlflow.api.proto.MlflowArtifacts.internal_static_mlflow_artifacts_DeleteArtifact_Response_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response.class, org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response.Builder.class);
+      }
+
+      private byte memoizedIsInitialized = -1;
+      @java.lang.Override
+      public final boolean isInitialized() {
+        byte isInitialized = memoizedIsInitialized;
+        if (isInitialized == 1) return true;
+        if (isInitialized == 0) return false;
+
+        memoizedIsInitialized = 1;
+        return true;
+      }
+
+      @java.lang.Override
+      public void writeTo(com.google.protobuf.CodedOutputStream output)
+                          throws java.io.IOException {
+        unknownFields.writeTo(output);
+      }
+
+      @java.lang.Override
+      public int getSerializedSize() {
+        int size = memoizedSize;
+        if (size != -1) return size;
+
+        size = 0;
+        size += unknownFields.getSerializedSize();
+        memoizedSize = size;
+        return size;
+      }
+
+      @java.lang.Override
+      public boolean equals(final java.lang.Object obj) {
+        if (obj == this) {
+         return true;
+        }
+        if (!(obj instanceof org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response)) {
+          return super.equals(obj);
+        }
+        org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response other = (org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response) obj;
+
+        if (!unknownFields.equals(other.unknownFields)) return false;
+        return true;
+      }
+
+      @java.lang.Override
+      public int hashCode() {
+        if (memoizedHashCode != 0) {
+          return memoizedHashCode;
+        }
+        int hash = 41;
+        hash = (19 * hash) + getDescriptor().hashCode();
+        hash = (29 * hash) + unknownFields.hashCode();
+        memoizedHashCode = hash;
+        return hash;
+      }
+
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parseFrom(
+          java.nio.ByteBuffer data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data);
+      }
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parseFrom(
+          java.nio.ByteBuffer data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data, extensionRegistry);
+      }
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parseFrom(
+          com.google.protobuf.ByteString data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data);
+      }
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parseFrom(
+          com.google.protobuf.ByteString data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data, extensionRegistry);
+      }
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parseFrom(byte[] data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data);
+      }
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parseFrom(
+          byte[] data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data, extensionRegistry);
+      }
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parseFrom(java.io.InputStream input)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseWithIOException(PARSER, input);
+      }
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parseFrom(
+          java.io.InputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseWithIOException(PARSER, input, extensionRegistry);
+      }
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parseDelimitedFrom(java.io.InputStream input)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseDelimitedWithIOException(PARSER, input);
+      }
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parseDelimitedFrom(
+          java.io.InputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+      }
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parseFrom(
+          com.google.protobuf.CodedInputStream input)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseWithIOException(PARSER, input);
+      }
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parseFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseWithIOException(PARSER, input, extensionRegistry);
+      }
+
+      @java.lang.Override
+      public Builder newBuilderForType() { return newBuilder(); }
+      public static Builder newBuilder() {
+        return DEFAULT_INSTANCE.toBuilder();
+      }
+      public static Builder newBuilder(org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response prototype) {
+        return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+      }
+      @java.lang.Override
+      public Builder toBuilder() {
+        return this == DEFAULT_INSTANCE
+            ? new Builder() : new Builder().mergeFrom(this);
+      }
+
+      @java.lang.Override
+      protected Builder newBuilderForType(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        Builder builder = new Builder(parent);
+        return builder;
+      }
+      /**
+       * Protobuf type {@code mlflow.artifacts.DeleteArtifact.Response}
+       */
+      public static final class Builder extends
+          com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+          // @@protoc_insertion_point(builder_implements:mlflow.artifacts.DeleteArtifact.Response)
+          org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.ResponseOrBuilder {
+        public static final com.google.protobuf.Descriptors.Descriptor
+            getDescriptor() {
+          return org.mlflow.api.proto.MlflowArtifacts.internal_static_mlflow_artifacts_DeleteArtifact_Response_descriptor;
+        }
+
+        @java.lang.Override
+        protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+            internalGetFieldAccessorTable() {
+          return org.mlflow.api.proto.MlflowArtifacts.internal_static_mlflow_artifacts_DeleteArtifact_Response_fieldAccessorTable
+              .ensureFieldAccessorsInitialized(
+                  org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response.class, org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response.Builder.class);
+        }
+
+        // Construct using org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response.newBuilder()
+        private Builder() {
+          maybeForceBuilderInitialization();
+        }
+
+        private Builder(
+            com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+          super(parent);
+          maybeForceBuilderInitialization();
+        }
+        private void maybeForceBuilderInitialization() {
+          if (com.google.protobuf.GeneratedMessageV3
+                  .alwaysUseFieldBuilders) {
+          }
+        }
+        @java.lang.Override
+        public Builder clear() {
+          super.clear();
+          return this;
+        }
+
+        @java.lang.Override
+        public com.google.protobuf.Descriptors.Descriptor
+            getDescriptorForType() {
+          return org.mlflow.api.proto.MlflowArtifacts.internal_static_mlflow_artifacts_DeleteArtifact_Response_descriptor;
+        }
+
+        @java.lang.Override
+        public org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response getDefaultInstanceForType() {
+          return org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response.getDefaultInstance();
+        }
+
+        @java.lang.Override
+        public org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response build() {
+          org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response result = buildPartial();
+          if (!result.isInitialized()) {
+            throw newUninitializedMessageException(result);
+          }
+          return result;
+        }
+
+        @java.lang.Override
+        public org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response buildPartial() {
+          org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response result = new org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response(this);
+          onBuilt();
+          return result;
+        }
+
+        @java.lang.Override
+        public Builder clone() {
+          return super.clone();
+        }
+        @java.lang.Override
+        public Builder setField(
+            com.google.protobuf.Descriptors.FieldDescriptor field,
+            java.lang.Object value) {
+          return super.setField(field, value);
+        }
+        @java.lang.Override
+        public Builder clearField(
+            com.google.protobuf.Descriptors.FieldDescriptor field) {
+          return super.clearField(field);
+        }
+        @java.lang.Override
+        public Builder clearOneof(
+            com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+          return super.clearOneof(oneof);
+        }
+        @java.lang.Override
+        public Builder setRepeatedField(
+            com.google.protobuf.Descriptors.FieldDescriptor field,
+            int index, java.lang.Object value) {
+          return super.setRepeatedField(field, index, value);
+        }
+        @java.lang.Override
+        public Builder addRepeatedField(
+            com.google.protobuf.Descriptors.FieldDescriptor field,
+            java.lang.Object value) {
+          return super.addRepeatedField(field, value);
+        }
+        @java.lang.Override
+        public Builder mergeFrom(com.google.protobuf.Message other) {
+          if (other instanceof org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response) {
+            return mergeFrom((org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response)other);
+          } else {
+            super.mergeFrom(other);
+            return this;
+          }
+        }
+
+        public Builder mergeFrom(org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response other) {
+          if (other == org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response.getDefaultInstance()) return this;
+          this.mergeUnknownFields(other.unknownFields);
+          onChanged();
+          return this;
+        }
+
+        @java.lang.Override
+        public final boolean isInitialized() {
+          return true;
+        }
+
+        @java.lang.Override
+        public Builder mergeFrom(
+            com.google.protobuf.CodedInputStream input,
+            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+            throws java.io.IOException {
+          org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response parsedMessage = null;
+          try {
+            parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+          } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            parsedMessage = (org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response) e.getUnfinishedMessage();
+            throw e.unwrapIOException();
+          } finally {
+            if (parsedMessage != null) {
+              mergeFrom(parsedMessage);
+            }
+          }
+          return this;
+        }
+        @java.lang.Override
+        public final Builder setUnknownFields(
+            final com.google.protobuf.UnknownFieldSet unknownFields) {
+          return super.setUnknownFields(unknownFields);
+        }
+
+        @java.lang.Override
+        public final Builder mergeUnknownFields(
+            final com.google.protobuf.UnknownFieldSet unknownFields) {
+          return super.mergeUnknownFields(unknownFields);
+        }
+
+
+        // @@protoc_insertion_point(builder_scope:mlflow.artifacts.DeleteArtifact.Response)
+      }
+
+      // @@protoc_insertion_point(class_scope:mlflow.artifacts.DeleteArtifact.Response)
+      private static final org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response DEFAULT_INSTANCE;
+      static {
+        DEFAULT_INSTANCE = new org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response();
+      }
+
+      public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response getDefaultInstance() {
+        return DEFAULT_INSTANCE;
+      }
+
+      @java.lang.Deprecated public static final com.google.protobuf.Parser<Response>
+          PARSER = new com.google.protobuf.AbstractParser<Response>() {
+        @java.lang.Override
+        public Response parsePartialFrom(
+            com.google.protobuf.CodedInputStream input,
+            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+            throws com.google.protobuf.InvalidProtocolBufferException {
+          return new Response(input, extensionRegistry);
+        }
+      };
+
+      public static com.google.protobuf.Parser<Response> parser() {
+        return PARSER;
+      }
+
+      @java.lang.Override
+      public com.google.protobuf.Parser<Response> getParserForType() {
+        return PARSER;
+      }
+
+      @java.lang.Override
+      public org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Response getDefaultInstanceForType() {
+        return DEFAULT_INSTANCE;
+      }
+
+    }
+
+    private byte memoizedIsInitialized = -1;
+    @java.lang.Override
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    @java.lang.Override
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      unknownFields.writeTo(output);
+    }
+
+    @java.lang.Override
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact)) {
+        return super.equals(obj);
+      }
+      org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact other = (org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact) obj;
+
+      if (!unknownFields.equals(other.unknownFields)) return false;
+      return true;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    @java.lang.Override
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    @java.lang.Override
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code mlflow.artifacts.DeleteArtifact}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:mlflow.artifacts.DeleteArtifact)
+        org.mlflow.api.proto.MlflowArtifacts.DeleteArtifactOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return org.mlflow.api.proto.MlflowArtifacts.internal_static_mlflow_artifacts_DeleteArtifact_descriptor;
+      }
+
+      @java.lang.Override
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return org.mlflow.api.proto.MlflowArtifacts.internal_static_mlflow_artifacts_DeleteArtifact_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.class, org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.Builder.class);
+      }
+
+      // Construct using org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      @java.lang.Override
+      public Builder clear() {
+        super.clear();
+        return this;
+      }
+
+      @java.lang.Override
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return org.mlflow.api.proto.MlflowArtifacts.internal_static_mlflow_artifacts_DeleteArtifact_descriptor;
+      }
+
+      @java.lang.Override
+      public org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact getDefaultInstanceForType() {
+        return org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.getDefaultInstance();
+      }
+
+      @java.lang.Override
+      public org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact build() {
+        org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      @java.lang.Override
+      public org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact buildPartial() {
+        org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact result = new org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact(this);
+        onBuilt();
+        return result;
+      }
+
+      @java.lang.Override
+      public Builder clone() {
+        return super.clone();
+      }
+      @java.lang.Override
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return super.setField(field, value);
+      }
+      @java.lang.Override
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return super.clearField(field);
+      }
+      @java.lang.Override
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return super.clearOneof(oneof);
+      }
+      @java.lang.Override
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return super.setRepeatedField(field, index, value);
+      }
+      @java.lang.Override
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return super.addRepeatedField(field, value);
+      }
+      @java.lang.Override
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact) {
+          return mergeFrom((org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact other) {
+        if (other == org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact.getDefaultInstance()) return this;
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      @java.lang.Override
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      @java.lang.Override
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      @java.lang.Override
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFields(unknownFields);
+      }
+
+      @java.lang.Override
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:mlflow.artifacts.DeleteArtifact)
+    }
+
+    // @@protoc_insertion_point(class_scope:mlflow.artifacts.DeleteArtifact)
+    private static final org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact();
+    }
+
+    public static org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    @java.lang.Deprecated public static final com.google.protobuf.Parser<DeleteArtifact>
+        PARSER = new com.google.protobuf.AbstractParser<DeleteArtifact>() {
+      @java.lang.Override
+      public DeleteArtifact parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new DeleteArtifact(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<DeleteArtifact> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<DeleteArtifact> getParserForType() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public org.mlflow.api.proto.MlflowArtifacts.DeleteArtifact getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
   public interface FileInfoOrBuilder extends
       // @@protoc_insertion_point(interface_extends:mlflow.artifacts.FileInfo)
       com.google.protobuf.MessageOrBuilder {
@@ -4193,6 +5029,16 @@ public org.mlflow.api.proto.MlflowArtifacts.FileInfo getDefaultInstanceForType()
   private static final 
     com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
       internal_static_mlflow_artifacts_ListArtifacts_Response_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_mlflow_artifacts_DeleteArtifact_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_mlflow_artifacts_DeleteArtifact_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_mlflow_artifacts_DeleteArtifact_Response_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_mlflow_artifacts_DeleteArtifact_Response_fieldAccessorTable;
   private static final com.google.protobuf.Descriptors.Descriptor
     internal_static_mlflow_artifacts_FileInfo_descriptor;
   private static final 
@@ -4212,24 +5058,29 @@ public org.mlflow.api.proto.MlflowArtifacts.FileInfo getDefaultInstanceForType()
       "oto\"\036\n\020DownloadArtifact\032\n\n\010Response\"\034\n\016U" +
       "ploadArtifact\032\n\n\010Response\"T\n\rListArtifac" +
       "ts\022\014\n\004path\030\001 \001(\t\0325\n\010Response\022)\n\005files\030\001 " +
-      "\003(\0132\032.mlflow.artifacts.FileInfo\";\n\010FileI" +
-      "nfo\022\014\n\004path\030\001 \001(\t\022\016\n\006is_dir\030\002 \001(\010\022\021\n\tfil" +
-      "e_size\030\003 \001(\0032\257\004\n\026MlflowArtifactsService\022" +
-      "\275\001\n\020downloadArtifact\022\".mlflow.artifacts." +
-      "DownloadArtifact\032+.mlflow.artifacts.Down" +
-      "loadArtifact.Response\"X\362\206\031T\n=\n\003GET\0220/mlf" +
-      "low-artifacts/artifacts/<path:artifact_p" +
-      "ath>\032\004\010\002\020\000\020\001*\021Download Artifact\022\265\001\n\016uplo" +
-      "adArtifact\022 .mlflow.artifacts.UploadArti" +
-      "fact\032).mlflow.artifacts.UploadArtifact.R" +
-      "esponse\"V\362\206\031R\n=\n\003PUT\0220/mlflow-artifacts/" +
-      "artifacts/<path:artifact_path>\032\004\010\002\020\000\020\001*\017" +
-      "Upload Artifact\022\234\001\n\rlistArtifacts\022\037.mlfl" +
-      "ow.artifacts.ListArtifacts\032(.mlflow.arti" +
-      "facts.ListArtifacts.Response\"@\362\206\031<\n(\n\003GE" +
-      "T\022\033/mlflow-artifacts/artifacts\032\004\010\002\020\000\020\001*\016" +
-      "List ArtifactsB\036\n\024org.mlflow.api.proto\220\001" +
-      "\001\342?\002\020\001"
+      "\003(\0132\032.mlflow.artifacts.FileInfo\"\034\n\016Delet" +
+      "eArtifact\032\n\n\010Response\";\n\010FileInfo\022\014\n\004pat" +
+      "h\030\001 \001(\t\022\016\n\006is_dir\030\002 \001(\010\022\021\n\tfile_size\030\003 \001" +
+      "(\0032\353\005\n\026MlflowArtifactsService\022\275\001\n\020downlo" +
+      "adArtifact\022\".mlflow.artifacts.DownloadAr" +
+      "tifact\032+.mlflow.artifacts.DownloadArtifa" +
+      "ct.Response\"X\362\206\031T\n=\n\003GET\0220/mlflow-artifa" +
+      "cts/artifacts/<path:artifact_path>\032\004\010\002\020\000" +
+      "\020\001*\021Download Artifact\022\265\001\n\016uploadArtifact" +
+      "\022 .mlflow.artifacts.UploadArtifact\032).mlf" +
+      "low.artifacts.UploadArtifact.Response\"V\362" +
+      "\206\031R\n=\n\003PUT\0220/mlflow-artifacts/artifacts/" +
+      "<path:artifact_path>\032\004\010\002\020\000\020\001*\017Upload Art" +
+      "ifact\022\234\001\n\rlistArtifacts\022\037.mlflow.artifac" +
+      "ts.ListArtifacts\032(.mlflow.artifacts.List" +
+      "Artifacts.Response\"@\362\206\031<\n(\n\003GET\022\033/mlflow" +
+      "-artifacts/artifacts\032\004\010\002\020\000\020\001*\016List Artif" +
+      "acts\022\271\001\n\016deleteArtifact\022 .mlflow.artifac" +
+      "ts.DeleteArtifact\032).mlflow.artifacts.Del" +
+      "eteArtifact.Response\"Z\362\206\031V\n@\n\006DELETE\0220/m" +
+      "lflow-artifacts/artifacts/<path:artifact" +
+      "_path>\032\004\010\002\020\000\020\001*\020Delete ArtifactsB\036\n\024org." +
+      "mlflow.api.proto\220\001\001\342?\002\020\001"
     };
     descriptor = com.google.protobuf.Descriptors.FileDescriptor
       .internalBuildGeneratedFileFrom(descriptorData,
@@ -4273,8 +5124,20 @@ public org.mlflow.api.proto.MlflowArtifacts.FileInfo getDefaultInstanceForType()
       com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
         internal_static_mlflow_artifacts_ListArtifacts_Response_descriptor,
         new java.lang.String[] { "Files", });
-    internal_static_mlflow_artifacts_FileInfo_descriptor =
+    internal_static_mlflow_artifacts_DeleteArtifact_descriptor =
       getDescriptor().getMessageTypes().get(3);
+    internal_static_mlflow_artifacts_DeleteArtifact_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_mlflow_artifacts_DeleteArtifact_descriptor,
+        new java.lang.String[] { });
+    internal_static_mlflow_artifacts_DeleteArtifact_Response_descriptor =
+      internal_static_mlflow_artifacts_DeleteArtifact_descriptor.getNestedTypes().get(0);
+    internal_static_mlflow_artifacts_DeleteArtifact_Response_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_mlflow_artifacts_DeleteArtifact_Response_descriptor,
+        new java.lang.String[] { });
+    internal_static_mlflow_artifacts_FileInfo_descriptor =
+      getDescriptor().getMessageTypes().get(4);
     internal_static_mlflow_artifacts_FileInfo_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
         internal_static_mlflow_artifacts_FileInfo_descriptor,
diff --git a/mlflow/protos/mlflow_artifacts.proto b/mlflow/protos/mlflow_artifacts.proto
index 5e8d04aea03a4..a0a618a275629 100644
--- a/mlflow/protos/mlflow_artifacts.proto
+++ b/mlflow/protos/mlflow_artifacts.proto
@@ -54,6 +54,18 @@ service MlflowArtifactsService {
       rpc_doc_title: "List Artifacts",
     };
   }
+
+  rpc deleteArtifact (DeleteArtifact) returns (DeleteArtifact.Response) {
+    option (rpc) = {
+      endpoints: [{
+        method: "DELETE",
+        path: "/mlflow-artifacts/artifacts/<path:artifact_path>",
+        since { major: 2, minor: 0 },
+      }],
+      visibility: PUBLIC,
+      rpc_doc_title: "Delete Artifacts",
+    };
+  }
 }
 
 message DownloadArtifact {
@@ -74,6 +86,10 @@ message ListArtifacts {
   }
 }
 
+message DeleteArtifact {
+  message Response {}
+}
+
 message FileInfo {
   // Path relative to the root artifact directory run.
   optional string path = 1;
diff --git a/mlflow/protos/mlflow_artifacts_pb2.py b/mlflow/protos/mlflow_artifacts_pb2.py
index 445862eea4413..6f579aaa69f13 100644
--- a/mlflow/protos/mlflow_artifacts_pb2.py
+++ b/mlflow/protos/mlflow_artifacts_pb2.py
@@ -18,7 +18,7 @@
 from . import databricks_pb2 as databricks__pb2
 
 
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16mlflow_artifacts.proto\x12\x10mlflow.artifacts\x1a\x15scalapb/scalapb.proto\x1a\x10\x64\x61tabricks.proto\"\x1e\n\x10\x44ownloadArtifact\x1a\n\n\x08Response\"\x1c\n\x0eUploadArtifact\x1a\n\n\x08Response\"T\n\rListArtifacts\x12\x0c\n\x04path\x18\x01 \x01(\t\x1a\x35\n\x08Response\x12)\n\x05\x66iles\x18\x01 \x03(\x0b\x32\x1a.mlflow.artifacts.FileInfo\";\n\x08\x46ileInfo\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x0e\n\x06is_dir\x18\x02 \x01(\x08\x12\x11\n\tfile_size\x18\x03 \x01(\x03\x32\xaf\x04\n\x16MlflowArtifactsService\x12\xbd\x01\n\x10\x64ownloadArtifact\x12\".mlflow.artifacts.DownloadArtifact\x1a+.mlflow.artifacts.DownloadArtifact.Response\"X\xf2\x86\x19T\n=\n\x03GET\x12\x30/mlflow-artifacts/artifacts/<path:artifact_path>\x1a\x04\x08\x02\x10\x00\x10\x01*\x11\x44ownload Artifact\x12\xb5\x01\n\x0euploadArtifact\x12 .mlflow.artifacts.UploadArtifact\x1a).mlflow.artifacts.UploadArtifact.Response\"V\xf2\x86\x19R\n=\n\x03PUT\x12\x30/mlflow-artifacts/artifacts/<path:artifact_path>\x1a\x04\x08\x02\x10\x00\x10\x01*\x0fUpload Artifact\x12\x9c\x01\n\rlistArtifacts\x12\x1f.mlflow.artifacts.ListArtifacts\x1a(.mlflow.artifacts.ListArtifacts.Response\"@\xf2\x86\x19<\n(\n\x03GET\x12\x1b/mlflow-artifacts/artifacts\x1a\x04\x08\x02\x10\x00\x10\x01*\x0eList ArtifactsB\x1e\n\x14org.mlflow.api.proto\x90\x01\x01\xe2?\x02\x10\x01')
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16mlflow_artifacts.proto\x12\x10mlflow.artifacts\x1a\x15scalapb/scalapb.proto\x1a\x10\x64\x61tabricks.proto\"\x1e\n\x10\x44ownloadArtifact\x1a\n\n\x08Response\"\x1c\n\x0eUploadArtifact\x1a\n\n\x08Response\"T\n\rListArtifacts\x12\x0c\n\x04path\x18\x01 \x01(\t\x1a\x35\n\x08Response\x12)\n\x05\x66iles\x18\x01 \x03(\x0b\x32\x1a.mlflow.artifacts.FileInfo\"\x1c\n\x0e\x44\x65leteArtifact\x1a\n\n\x08Response\";\n\x08\x46ileInfo\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x0e\n\x06is_dir\x18\x02 \x01(\x08\x12\x11\n\tfile_size\x18\x03 \x01(\x03\x32\xeb\x05\n\x16MlflowArtifactsService\x12\xbd\x01\n\x10\x64ownloadArtifact\x12\".mlflow.artifacts.DownloadArtifact\x1a+.mlflow.artifacts.DownloadArtifact.Response\"X\xf2\x86\x19T\n=\n\x03GET\x12\x30/mlflow-artifacts/artifacts/<path:artifact_path>\x1a\x04\x08\x02\x10\x00\x10\x01*\x11\x44ownload Artifact\x12\xb5\x01\n\x0euploadArtifact\x12 .mlflow.artifacts.UploadArtifact\x1a).mlflow.artifacts.UploadArtifact.Response\"V\xf2\x86\x19R\n=\n\x03PUT\x12\x30/mlflow-artifacts/artifacts/<path:artifact_path>\x1a\x04\x08\x02\x10\x00\x10\x01*\x0fUpload Artifact\x12\x9c\x01\n\rlistArtifacts\x12\x1f.mlflow.artifacts.ListArtifacts\x1a(.mlflow.artifacts.ListArtifacts.Response\"@\xf2\x86\x19<\n(\n\x03GET\x12\x1b/mlflow-artifacts/artifacts\x1a\x04\x08\x02\x10\x00\x10\x01*\x0eList Artifacts\x12\xb9\x01\n\x0e\x64\x65leteArtifact\x12 .mlflow.artifacts.DeleteArtifact\x1a).mlflow.artifacts.DeleteArtifact.Response\"Z\xf2\x86\x19V\n@\n\x06\x44\x45LETE\x12\x30/mlflow-artifacts/artifacts/<path:artifact_path>\x1a\x04\x08\x02\x10\x00\x10\x01*\x10\x44\x65lete ArtifactsB\x1e\n\x14org.mlflow.api.proto\x90\x01\x01\xe2?\x02\x10\x01')
 
 
 
@@ -28,6 +28,8 @@
 _UPLOADARTIFACT_RESPONSE = _UPLOADARTIFACT.nested_types_by_name['Response']
 _LISTARTIFACTS = DESCRIPTOR.message_types_by_name['ListArtifacts']
 _LISTARTIFACTS_RESPONSE = _LISTARTIFACTS.nested_types_by_name['Response']
+_DELETEARTIFACT = DESCRIPTOR.message_types_by_name['DeleteArtifact']
+_DELETEARTIFACT_RESPONSE = _DELETEARTIFACT.nested_types_by_name['Response']
 _FILEINFO = DESCRIPTOR.message_types_by_name['FileInfo']
 DownloadArtifact = _reflection.GeneratedProtocolMessageType('DownloadArtifact', (_message.Message,), {
 
@@ -74,6 +76,21 @@
 _sym_db.RegisterMessage(ListArtifacts)
 _sym_db.RegisterMessage(ListArtifacts.Response)
 
+DeleteArtifact = _reflection.GeneratedProtocolMessageType('DeleteArtifact', (_message.Message,), {
+
+  'Response' : _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), {
+    'DESCRIPTOR' : _DELETEARTIFACT_RESPONSE,
+    '__module__' : 'mlflow_artifacts_pb2'
+    # @@protoc_insertion_point(class_scope:mlflow.artifacts.DeleteArtifact.Response)
+    })
+  ,
+  'DESCRIPTOR' : _DELETEARTIFACT,
+  '__module__' : 'mlflow_artifacts_pb2'
+  # @@protoc_insertion_point(class_scope:mlflow.artifacts.DeleteArtifact)
+  })
+_sym_db.RegisterMessage(DeleteArtifact)
+_sym_db.RegisterMessage(DeleteArtifact.Response)
+
 FileInfo = _reflection.GeneratedProtocolMessageType('FileInfo', (_message.Message,), {
   'DESCRIPTOR' : _FILEINFO,
   '__module__' : 'mlflow_artifacts_pb2'
@@ -92,6 +109,8 @@
   _MLFLOWARTIFACTSSERVICE.methods_by_name['uploadArtifact']._serialized_options = b'\362\206\031R\n=\n\003PUT\0220/mlflow-artifacts/artifacts/<path:artifact_path>\032\004\010\002\020\000\020\001*\017Upload Artifact'
   _MLFLOWARTIFACTSSERVICE.methods_by_name['listArtifacts']._options = None
   _MLFLOWARTIFACTSSERVICE.methods_by_name['listArtifacts']._serialized_options = b'\362\206\031<\n(\n\003GET\022\033/mlflow-artifacts/artifacts\032\004\010\002\020\000\020\001*\016List Artifacts'
+  _MLFLOWARTIFACTSSERVICE.methods_by_name['deleteArtifact']._options = None
+  _MLFLOWARTIFACTSSERVICE.methods_by_name['deleteArtifact']._serialized_options = b'\362\206\031V\n@\n\006DELETE\0220/mlflow-artifacts/artifacts/<path:artifact_path>\032\004\010\002\020\000\020\001*\020Delete Artifacts'
   _DOWNLOADARTIFACT._serialized_start=85
   _DOWNLOADARTIFACT._serialized_end=115
   _DOWNLOADARTIFACT_RESPONSE._serialized_start=105
@@ -104,10 +123,14 @@
   _LISTARTIFACTS._serialized_end=231
   _LISTARTIFACTS_RESPONSE._serialized_start=178
   _LISTARTIFACTS_RESPONSE._serialized_end=231
-  _FILEINFO._serialized_start=233
-  _FILEINFO._serialized_end=292
-  _MLFLOWARTIFACTSSERVICE._serialized_start=295
-  _MLFLOWARTIFACTSSERVICE._serialized_end=854
+  _DELETEARTIFACT._serialized_start=233
+  _DELETEARTIFACT._serialized_end=261
+  _DELETEARTIFACT_RESPONSE._serialized_start=105
+  _DELETEARTIFACT_RESPONSE._serialized_end=115
+  _FILEINFO._serialized_start=263
+  _FILEINFO._serialized_end=322
+  _MLFLOWARTIFACTSSERVICE._serialized_start=325
+  _MLFLOWARTIFACTSSERVICE._serialized_end=1072
 MlflowArtifactsService = service_reflection.GeneratedServiceType('MlflowArtifactsService', (_service.Service,), dict(
   DESCRIPTOR = _MLFLOWARTIFACTSSERVICE,
   __module__ = 'mlflow_artifacts_pb2'
diff --git a/mlflow/server/handlers.py b/mlflow/server/handlers.py
index d3a530841e056..4a722a1f353a8 100644
--- a/mlflow/server/handlers.py
+++ b/mlflow/server/handlers.py
@@ -72,6 +72,7 @@
     DownloadArtifact,
     UploadArtifact,
     ListArtifacts as ListArtifactsMlflowArtifacts,
+    DeleteArtifact,
 )
 from mlflow.protos.databricks_pb2 import RESOURCE_DOES_NOT_EXIST, INVALID_PARAMETER_VALUE
 from mlflow.store.artifact.artifact_repository_registry import get_artifact_repository
@@ -1476,6 +1477,22 @@ def _list_artifacts_mlflow_artifacts():
     return response
 
 
+@catch_mlflow_exception
+@_disable_unless_serve_artifacts
+def _delete_artifact_mflflow_artifacts(artifact_path):
+    """
+    A request handler for `DELETE /mlflow-artifacts/artifacts?path=<value>` to delete artifacts in
+    `path` (a relative path from the root artifact directory).
+    """
+    _get_request_message(DeleteArtifact())
+    artifact_repo = _get_artifact_repo_mlflow_artifacts()
+    artifact_repo.delete_artifacts(artifact_path)
+    response_message = DeleteArtifact.Response()
+    response = Response(mimetype="application/json")
+    response.set_data(message_to_json(response_message))
+    return response
+
+
 def _add_static_prefix(route):
     prefix = os.environ.get(STATIC_PREFIX_ENV_VAR)
     if prefix:
@@ -1570,4 +1587,5 @@ def get_service_endpoints(service):
     DownloadArtifact: _download_artifact,
     UploadArtifact: _upload_artifact,
     ListArtifactsMlflowArtifacts: _list_artifacts_mlflow_artifacts,
+    DeleteArtifact: _delete_artifact_mflflow_artifacts,
 }
diff --git a/mlflow/store/artifact/http_artifact_repo.py b/mlflow/store/artifact/http_artifact_repo.py
index 2eaf7327a7b70..0ef847f0a6238 100644
--- a/mlflow/store/artifact/http_artifact_repo.py
+++ b/mlflow/store/artifact/http_artifact_repo.py
@@ -66,3 +66,11 @@ def _download_file(self, remote_file_path, local_path):
             chunk_size = 1024 * 1024  # 1 MB
             for chunk in resp.iter_content(chunk_size=chunk_size):
                 f.write(chunk)
+
+    def delete_artifacts(self, artifact_path=""):
+        endpoint = posixpath.join("/", artifact_path)
+        resp = http_request(self._host_creds, endpoint, "DELETE", stream=True, timeout=10)
+        augmented_raise_for_status(resp)
+        if resp.status_code == 200:
+            return True
+        return False
diff --git a/tests/store/artifact/test_http_artifact_repo.py b/tests/store/artifact/test_http_artifact_repo.py
index 9586ded8f3837..6d6d770fee70c 100644
--- a/tests/store/artifact/test_http_artifact_repo.py
+++ b/tests/store/artifact/test_http_artifact_repo.py
@@ -285,3 +285,19 @@ def test_default_host_creds():
         },
     ):
         assert repo._host_creds == expected_host_creds
+
+
+@pytest.mark.parametrize("remote_file_path", ["", "a.txt", "dir/b.txt"])
+def test_delete_artifacts(http_artifact_repo, remote_file_path):
+    with mock.patch(
+        "mlflow.store.artifact.http_artifact_repo.http_request",
+        return_value=MockStreamResponse("data", 200),
+    ) as mock_get:
+        http_artifact_repo.delete_artifacts(remote_file_path)
+        mock_get.assert_called_once_with(
+            http_artifact_repo._host_creds,
+            posixpath.join("/", remote_file_path),
+            "DELETE",
+            stream=True,
+            timeout=mock.ANY,
+        )


Code review comments:
diff_hunk: @@ -66,3 +66,11 @@ def _download_file(self, remote_file_path, local_path):
             chunk_size = 1024 * 1024  # 1 MB
             for chunk in resp.iter_content(chunk_size=chunk_size):
                 f.write(chunk)
+
+    def delete_artifacts(self, artifact_path=""):
path: mlflow/store/artifact/http_artifact_repo.py
text: @user1:
```suggestion
    def delete_artifacts(self, artifact_path=None):
```

diff_hunk: @@ -66,3 +66,11 @@ def _download_file(self, remote_file_path, local_path):
             chunk_size = 1024 * 1024  # 1 MB
             for chunk in resp.iter_content(chunk_size=chunk_size):
                 f.write(chunk)
+
+    def delete_artifacts(self, artifact_path=""):
+        endpoint = posixpath.join("/", artifact_path)
+        resp = http_request(self._host_creds, endpoint, "DELETE", stream=True, timeout=10)
+        augmented_raise_for_status(resp)
+        if resp.status_code == 200:
+            return True
+        return False
path: mlflow/store/artifact/http_artifact_repo.py
text: @user1:
Do we need to return True/False?

@author:
No, I will remove it.