Repository: gojek/feast (java)

Issue:
Allow create_project to rerun safely
**Is your feature request related to a problem? Please describe.**
Having the `create_project` method on the Feast Client fail when a project exists is important, but its also a pain in development. It means one part of the code in a notebook should be skipped every time.

**Describe the solution you'd like**
It would be convenient to be able to run `create_project` multiple times without having the second call throw an exception.

Something like
```
client.create_project("my-project", skip_if_exists=True)
```

**Describe alternatives you've considered**
Alternatively we can use `set_project` and only create the project lazily when its needed.


Pull request:
Create project if not exists on applyFeatureSet
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. Ensure that your code follows our code conventions: https://github.com/gojek/feast/blob/master/docs/contributing.md#code-conventions
2. Run unit tests and ensure that they are passing: https://github.com/gojek/feast/blob/master/docs/contributing.md#running-unit-tests
3. If your change introduces any API changes, make sure to update the integration tests scripts here: https://github.com/gojek/feast/tree/master/tests/e2e
4. Make sure documentation is updated for your PR!
5. Make sure you have signed the CLA https://cla.developers.google.com/clas

-->

**What this PR does / why we need it**:
Allow create_project to rerun safely #453. Instead of having to run `create_project` before apply, the apply method can create the project if it does not already exist.
**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #453 

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information about release notes, see kubernetes' guide here:
http://git.k8s.io/community/contributors/guide/release-notes.md
-->
```release-note
When applying a featureSet to a project that does not yet exist, Feast create the project on apply.
```


Patch:
diff --git a/core/src/main/java/feast/core/service/SpecService.java b/core/src/main/java/feast/core/service/SpecService.java
index 5b98d0659..8fec6ac51 100644
--- a/core/src/main/java/feast/core/service/SpecService.java
+++ b/core/src/main/java/feast/core/service/SpecService.java
@@ -288,19 +288,14 @@ public class SpecService {
     // Validate incoming feature set
     FeatureSetValidator.validateSpec(newFeatureSet);
 
-    // Ensure that the project already exists
+    // Find project or create new one if it does not exist
     String project_name = newFeatureSet.getSpec().getProject();
     Project project =
         projectRepository
             .findById(newFeatureSet.getSpec().getProject())
-            .orElseThrow(
-                () ->
-                    new IllegalArgumentException(
-                        String.format(
-                            "Project name does not exist. Please create a project first: %s",
-                            project_name)));
-
-    // Ensure that the project is not archived
+            .orElse(new Project(project_name));
+
+    // Ensure that the project retrieved from repository is not archived
     if (project.isArchived()) {
       throw new IllegalArgumentException(String.format("Project is archived: %s", project_name));
     }


Test patch:
diff --git a/core/src/test/java/feast/core/service/SpecServiceTest.java b/core/src/test/java/feast/core/service/SpecServiceTest.java
index 1eb56caac..43a66135d 100644
--- a/core/src/test/java/feast/core/service/SpecServiceTest.java
+++ b/core/src/test/java/feast/core/service/SpecServiceTest.java
@@ -170,6 +170,10 @@ public class SpecServiceTest {
     when(projectRepository.findAllByArchivedIsFalse())
         .thenReturn(Collections.singletonList(new Project("project1")));
     when(projectRepository.findById("project1")).thenReturn(Optional.of(new Project("project1")));
+    Project archivedProject = new Project("archivedproject");
+    archivedProject.setArchived(true);
+    when(projectRepository.findById(archivedProject.getName()))
+        .thenReturn(Optional.of(archivedProject));
 
     Store store1 = newDummyStore("SERVING");
     Store store2 = newDummyStore("WAREHOUSE");
@@ -706,6 +710,55 @@ public class SpecServiceTest {
     }
   }
 
+  @Test
+  public void applyFeatureSetShouldCreateProjectWhenNotAlreadyExists()
+      throws InvalidProtocolBufferException {
+    Field f3f1 = new Field("f3f1", Enum.INT64);
+    Field f3f2 = new Field("f3f2", Enum.INT64);
+    Field f3e1 = new Field("f3e1", Enum.STRING);
+    FeatureSetProto.FeatureSet incomingFeatureSet =
+        (new FeatureSet(
+                "f3",
+                "newproject",
+                5,
+                100L,
+                Arrays.asList(f3e1),
+                Arrays.asList(f3f2, f3f1),
+                defaultSource,
+                FeatureSetStatus.STATUS_READY))
+            .toProto();
+
+    ApplyFeatureSetResponse applyFeatureSetResponse =
+        specService.applyFeatureSet(incomingFeatureSet);
+    assertThat(applyFeatureSetResponse.getStatus(), equalTo(Status.CREATED));
+    assertThat(
+        applyFeatureSetResponse.getFeatureSet().getSpec().getProject(),
+        equalTo(incomingFeatureSet.getSpec().getProject()));
+  }
+
+  @Test
+  public void applyFeatureSetShouldFailWhenProjectIsArchived()
+      throws InvalidProtocolBufferException {
+    Field f3f1 = new Field("f3f1", Enum.INT64);
+    Field f3f2 = new Field("f3f2", Enum.INT64);
+    Field f3e1 = new Field("f3e1", Enum.STRING);
+    FeatureSetProto.FeatureSet incomingFeatureSet =
+        (new FeatureSet(
+                "f3",
+                "archivedproject",
+                5,
+                100L,
+                Arrays.asList(f3e1),
+                Arrays.asList(f3f2, f3f1),
+                defaultSource,
+                FeatureSetStatus.STATUS_READY))
+            .toProto();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Project is archived");
+    specService.applyFeatureSet(incomingFeatureSet);
+  }
+
   @Test
   public void shouldUpdateStoreIfConfigChanges() throws InvalidProtocolBufferException {
     when(storeRepository.findById("SERVING")).thenReturn(Optional.of(stores.get(0)));