Repository: jcrist/skein (python)
Issue: `submit_and_connect` doesn't work on 0.4.0 when a file path is used
Need to convert spec into a `ApplicationSpec` object before calling `self.submit(spec)`.
Pull request description: Normalize spec before submitting in submit_and_connect
Previously this wasn't done, leading to errors if the spec wasn't already an ApplicationSpec.

Fixes #110.

Patch:
diff --git a/java/pom.xml b/java/pom.xml
index 906064f..1166ef8 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -93,10 +93,6 @@
               </filters>
 
               <relocations>
-                <relocation>
-                  <pattern>javax.*</pattern>
-                  <shadedPattern>com.anaconda.skein.shaded.javax</shadedPattern>
-                </relocation>
                 <relocation>
                   <pattern>com.google</pattern>
                   <shadedPattern>com.anaconda.skein.shaded.com.google</shadedPattern>
diff --git a/skein/core.py b/skein/core.py
index 2996a37..a0823d9 100644
--- a/skein/core.py
+++ b/skein/core.py
@@ -411,14 +411,7 @@ class Client(_ClientBase):
         app_id : str
             The id of the submitted application.
         """
-        if isinstance(spec, str):
-            spec = ApplicationSpec.from_file(spec)
-        elif isinstance(spec, dict):
-            spec = ApplicationSpec.from_dict(spec)
-        elif not isinstance(spec, ApplicationSpec):
-            raise context.TypeError("spec must be either an ApplicationSpec, "
-                                    "path, or dict, got "
-                                    "%s" % type(spec).__name__)
+        spec = ApplicationSpec._from_any(spec)
         resp = self._call('submit', spec.to_protobuf())
         return resp.id
 
@@ -439,6 +432,7 @@ class Client(_ClientBase):
         -------
         app_client : ApplicationClient
         """
+        spec = ApplicationSpec._from_any(spec)
         app_id = self.submit(spec)
         try:
             return self.connect(app_id, security=spec.master.security)
diff --git a/skein/model.py b/skein/model.py
index 4444e79..8cd457a 100644
--- a/skein/model.py
+++ b/skein/model.py
@@ -1118,6 +1118,19 @@ class ApplicationSpec(Specification):
                         for name, service in self.services.items()}
         check_no_cycles(dependencies)
 
+    @classmethod
+    def _from_any(cls, spec):
+        """Generic creation method for all types accepted as ``spec``"""
+        if isinstance(spec, str):
+            spec = cls.from_file(spec)
+        elif isinstance(spec, dict):
+            spec = cls.from_dict(spec)
+        elif not isinstance(spec, cls):
+            raise context.TypeError("spec must be either an ApplicationSpec, "
+                                    "path, or dict, got "
+                                    "%s" % type(spec).__name__)
+        return spec
+
     @classmethod
     @implements(Specification.from_dict)
     def from_dict(cls, obj, **kwargs):


Test patch:
diff --git a/skein/test/test_model.py b/skein/test/test_model.py
index b651cc1..23167e4 100644
--- a/skein/test/test_model.py
+++ b/skein/test/test_model.py
@@ -475,6 +475,20 @@ def test_to_file_from_file(tmpdir):
         assert not os.path.exists(path)
 
 
+def test_application_spec_from_any(tmpdir):
+    spec = ApplicationSpec.from_yaml(app_spec)
+    spec_path = os.path.join(str(tmpdir), 'test.yaml')
+    spec.to_file(spec_path)
+    spec_dict = spec.to_dict()
+
+    for obj in [spec, spec_path, spec_dict]:
+        spec2 = ApplicationSpec._from_any(obj)
+        assert spec == spec2
+
+    with pytest.raises(TypeError):
+        ApplicationSpec._from_any(None)
+
+
 def test_enums():
     assert type(ApplicationState.RUNNING) is ApplicationState
     assert ApplicationState.RUNNING is ApplicationState('RUNNING')