Repository: kubeflow/pipelines

Issue:
client.create_experiment and client.get_experiment treat differences in case differently
### What steps did you take:

The following code snippet tries to create two experiments. The experiment names only differ in case, e.g. `Untitled` vs `untitled`

```
import kfp
client = kfp.Client(host='http://.../pipeline')
# create experiment succeeds if there is no experiment named `untitled` (ignoring case)
experiment = client.create_experiment('Untitled', namespace='anonymous')
# create experiment fails 
experiment = client.create_experiment('untitled', namespace='anonymous')

# output > Create experiment failed.: Already exist error: Failed to create a new experiment. The name untitled already exists. Please specify a new name.

experiment = client.get_experiment('untitled', namespace='anonymous')

# output > ValueError: No experiment is found with name untitled.
```

### What happened:

Create experiment fails because an experiment with the name already exists.

### What did you expect to happen:

`get_experiment` and `create_experiment` behave the same (ignore case or honor it).

### Environment:
<!-- Please fill in those that seem relevant. -->

How did you deploy Kubeflow Pipelines (KFP)?
<!-- If you are not sure, here's [an introduction of all options](https://www.kubeflow.org/docs/pipelines/installation/overview/). -->

KFP version: 1.0.4

KFP SDK version: 1.3.0


### Anything else you would like to add:
 Found https://github.com/kubeflow/pipelines/issues/2240 but the issue is stale.

/kind bug
<!-- Please include labels by uncommenting them to help us better triage issues, choose from the following -->
<!--
// /area frontend
// /area backend
/area sdk
// /area testing
// /area engprod
-->



Discussion:
Makes sense, I think we should distinguish case when creating an experiment too.
Welcome contribution on this change.
/assign

Resolving patch:
diff --git a/sdk/python/kfp/_client.py b/sdk/python/kfp/_client.py
--- a/sdk/python/kfp/_client.py
+++ b/sdk/python/kfp/_client.py
@@ -452,14 +452,27 @@ def get_experiment(self, experiment_id=None, experiment_name=None, namespace=Non
       raise ValueError('Either experiment_id or experiment_name is required')
     if experiment_id is not None:
       return self._experiment_api.get_experiment(id=experiment_id)
-    next_page_token = ''
-    while next_page_token is not None:
-      list_experiments_response = self.list_experiments(page_size=100, page_token=next_page_token, namespace=namespace)
-      next_page_token = list_experiments_response.next_page_token
-      for experiment in list_experiments_response.experiments or []:
-        if experiment.name == experiment_name:
-          return self._experiment_api.get_experiment(id=experiment.id)
-    raise ValueError('No experiment is found with name {}.'.format(experiment_name))
+    experiment_filter = json.dumps({ 
+        "predicates": [ 
+          { 
+            "op":  _FILTER_OPERATIONS["EQUALS"], 
+            "key": "name", 
+            "stringValue": experiment_name, 
+          }
+        ] 
+      })
+    if namespace:
+      result = self._experiment_api.list_experiment(
+        filter=experiment_filter,
+        resource_reference_key_type=kfp_server_api.models.api_resource_type.ApiResourceType.NAMESPACE, 
+        resource_reference_key_id=namespace)
+    else:
+      result = self._experiment_api.list_experiment(filter=experiment_filter)
+    if not result.experiments:
+      raise ValueError('No experiment is found with name {}.'.format(experiment_name))
+    if len(result.experiments) > 1:
+      raise ValueError('Multiple experiments is found with name {}.'.format(experiment_name))
+    return result.experiments[0]
 
   def delete_experiment(self, experiment_id):
     """Delete experiment.


Test patch: