Support python actions with zip files.

Refactoring of init method into an init-from-source and init-from-zip.
Support a zip file containing more than one python file.
The zip file must contain a file called __main__.py that defines a "main" method.

Log an error when python zip does not include required file.
diff --git a/tests/src/actionContainers/PythonActionContainerTests.scala b/tests/src/actionContainers/PythonActionContainerTests.scala
index e62b7b3..ce25ffc 100644
--- a/tests/src/actionContainers/PythonActionContainerTests.scala
+++ b/tests/src/actionContainers/PythonActionContainerTests.scala
@@ -20,6 +20,8 @@
 import org.scalatest.junit.JUnitRunner
 
 import ActionContainer.withContainer
+import ResourceHelpers.ZipBuilder
+
 import spray.json.DefaultJsonProtocol._
 import spray.json._
 import common.WskActorSystem
@@ -79,6 +81,59 @@
         }
     }
 
+    it should "support zip-encoded action using non-default entry points" in {
+        val srcs = Seq(
+            Seq("__main__.py") -> """
+                |from echo import echo
+                |def niam(args):
+                |    return echo(args)
+            """.stripMargin,
+            Seq("echo.py") -> """
+                |def echo(args):
+                |  return { "echo": args }
+            """.stripMargin)
+
+        val code = ZipBuilder.mkBase64Zip(srcs)
+
+        val (out, err) = withActionContainer() { c =>
+            val (initCode, initRes) = c.init(initPayload(code, main = "niam"))
+            initCode should be(200)
+
+            val args = JsObject("msg" -> JsString("it works"))
+            val (runCode, runRes) = c.run(runPayload(args))
+
+            runCode should be(200)
+            runRes.get.fields.get("echo") shouldBe Some(args)
+        }
+
+        checkStreams(out, err, {
+            case (o, e) =>
+                o shouldBe empty
+                e shouldBe empty
+        })
+    }
+
+    it should "report error if zip-encoded action does not include required file" in {
+        val srcs = Seq(
+            Seq("echo.py") -> """
+                |def echo(args):
+                |  return { "echo": args }
+            """.stripMargin)
+
+        val code = ZipBuilder.mkBase64Zip(srcs)
+
+        val (out, err) = withActionContainer() { c =>
+            val (initCode, initRes) = c.init(initPayload(code, main = "echo"))
+            initCode should be(502)
+        }
+
+        checkStreams(out, err, {
+            case (o, e) =>
+                o shouldBe empty
+                e should include("Zip file does not include")
+        })
+    }
+
     it should "handle unicode in source, input params, logs, and result" in {
         val (out, err) = withActionContainer() { c =>
             val code = """
@@ -140,9 +195,6 @@
             val (initCode, res) = c.init(initPayload(code))
             // init checks whether compilation was successful, so return 502
             initCode should be(502)
-
-            val (runCode, runRes) = c.run(runPayload(JsObject("basic" -> JsString("forever"))))
-            runCode should be(502)
         }
 
         checkStreams(out, err, {
diff --git a/tests/src/actionContainers/SwiftActionContainerTests.scala b/tests/src/actionContainers/SwiftActionContainerTests.scala
index 73f6a20..85a4eb0 100644
--- a/tests/src/actionContainers/SwiftActionContainerTests.scala
+++ b/tests/src/actionContainers/SwiftActionContainerTests.scala
@@ -162,9 +162,6 @@
 
             val (initCode, _) = c.init(initPayload(code))
             initCode should not be (200)
-
-            val (runCode, runRes) = c.run(runPayload(JsObject("basic" -> JsString("forever"))))
-            runCode should be(502)
         }
 
         checkStreams(out, err, {
diff --git a/tests/src/system/basic/CLIPythonTests.scala b/tests/src/system/basic/CLIPythonTests.scala
index 9369e65..ff7ea82 100644
--- a/tests/src/system/basic/CLIPythonTests.scala
+++ b/tests/src/system/basic/CLIPythonTests.scala
@@ -64,6 +64,18 @@
             }
     }
 
+    it should "invoke an action from a zip file with a non-default entry point" in withAssetCleaner(wskprops) {
+        (wp, assetHelper) =>
+            val name = "pythonZipWithNonDefaultEntryPoint"
+            assetHelper.withCleaner(wsk.action, name) {
+                (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("python.zip")), main = Some("niam"), kind = Some("python"))
+            }
+
+            withActivation(wsk.activation, wsk.action.invoke(name, Map("name" -> "Prince".toJson))) {
+                _.response.result.get shouldBe JsObject("greeting" -> JsString("Hello Prince!"))
+            }
+    }
+
     it should "invoke an action and confirm expected environment is defined" in withAssetCleaner(wskprops) {
         (wp, assetHelper) =>
             val name = "stdenv"