We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
GPL License Violation in the kserve python package
The version of the `kserve` package that is currently on PyPI (version `0.10`) violates the GPL license because it depends on [`table-logger`](https://github.com/AleksTk/table-logger), distributed under GPLv2 (you'll see that the library is now MIT, the author updated the license just a few days ago, but hasn't released a new version with the new license yet). No GPLv2 packages should be imported given that `kserve` has an Apache 2 license.


This was recently fixed by this PR https://github.com/kserve/kserve/pull/2673, which accidentally resolved the issue by replacing `table-logger` with `tabulate` (MIT License)

cc @yuzisun @cliveseldon @jinchihe @ellistarn 

Is it possible to quickly release a patch release `0.10.1` to include the above patch and make sure `kserve` is compliant with the Apache license? As it stands, any distribution and vendor using `kserve` is liable for a license violation.





--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `python/kserve/kserve/api/watch.py`
Content:
```
1 # Copyright 2021 The KServe Authors.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #    http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import time
16 from kubernetes import client
17 from kubernetes import watch as k8s_watch
18 from table_logger import TableLogger
19 
20 from ..constants import constants
21 from ..utils import utils
22 
23 
24 def isvc_watch(name=None, namespace=None, timeout_seconds=600, generation=0):
25     """Watch the created or patched InferenceService in the specified namespace"""
26 
27     if namespace is None:
28         namespace = utils.get_default_target_namespace()
29 
30     tbl = TableLogger(
31         columns='NAME,READY,PREV,LATEST,URL',
32         colwidth={'NAME': 20, 'READY': 10, 'PREV': 25, 'LATEST': 25, 'URL': 65},
33         border=False)
34 
35     stream = k8s_watch.Watch().stream(
36         client.CustomObjectsApi().list_namespaced_custom_object,
37         constants.KSERVE_GROUP,
38         constants.KSERVE_V1BETA1_VERSION,
39         namespace,
40         constants.KSERVE_PLURAL,
41         timeout_seconds=timeout_seconds)
42 
43     for event in stream:
44         isvc = event['object']
45         isvc_name = isvc['metadata']['name']
46         if name and name != isvc_name:
47             continue
48         else:
49             status = 'Unknown'
50             if isvc.get('status', ''):
51                 url = isvc['status'].get('url', '')
52                 traffic = isvc['status'].get('components', {}).get(
53                     'predictor', {}).get('traffic', [])
54                 traffic_percent = 100
55                 if constants.OBSERVED_GENERATION in isvc['status']:
56                     observed_generation = isvc['status'][constants.OBSERVED_GENERATION]
57                     for t in traffic:
58                         if t["latestRevision"]:
59                             traffic_percent = t["percent"]
60 
61                     if generation != 0 and observed_generation != generation:
62                         continue
63                     for condition in isvc['status'].get('conditions', {}):
64                         if condition.get('type', '') == 'Ready':
65                             status = condition.get('status', 'Unknown')
66                     tbl(isvc_name, status, 100-traffic_percent, traffic_percent, url)
67                     if status == 'True':
68                         break
69 
70             else:
71                 tbl(isvc_name, status, '', '', '')
72                 # Sleep 2 to avoid status section is not generated within a very short time.
73                 time.sleep(2)
74                 continue
75
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/python/kserve/kserve/api/watch.py b/python/kserve/kserve/api/watch.py
--- a/python/kserve/kserve/api/watch.py
+++ b/python/kserve/kserve/api/watch.py
@@ -13,9 +13,10 @@
 # limitations under the License.
 
 import time
+
 from kubernetes import client
 from kubernetes import watch as k8s_watch
-from table_logger import TableLogger
+from tabulate import tabulate
 
 from ..constants import constants
 from ..utils import utils
@@ -27,10 +28,8 @@
     if namespace is None:
         namespace = utils.get_default_target_namespace()
 
-    tbl = TableLogger(
-        columns='NAME,READY,PREV,LATEST,URL',
-        colwidth={'NAME': 20, 'READY': 10, 'PREV': 25, 'LATEST': 25, 'URL': 65},
-        border=False)
+    headers = ['NAME', 'READY', 'PREV', 'LATEST', 'URL']
+    table_fmt = 'plain'
 
     stream = k8s_watch.Watch().stream(
         client.CustomObjectsApi().list_namespaced_custom_object,
@@ -63,12 +62,13 @@
                     for condition in isvc['status'].get('conditions', {}):
                         if condition.get('type', '') == 'Ready':
                             status = condition.get('status', 'Unknown')
-                    tbl(isvc_name, status, 100-traffic_percent, traffic_percent, url)
+                    print(tabulate([[isvc_name, status, 100 - traffic_percent, traffic_percent, url]],
+                                   headers=headers, tablefmt=table_fmt))
                     if status == 'True':
                         break
 
             else:
-                tbl(isvc_name, status, '', '', '')
+                print(tabulate([[isvc_name, status, '', '', '']], headers=headers, tablefmt=table_fmt))
                 # Sleep 2 to avoid status section is not generated within a very short time.
                 time.sleep(2)
                 continue

{"golden_diff": "diff --git a/python/kserve/kserve/api/watch.py b/python/kserve/kserve/api/watch.py\n--- a/python/kserve/kserve/api/watch.py\n+++ b/python/kserve/kserve/api/watch.py\n@@ -13,9 +13,10 @@\n # limitations under the License.\n \n import time\n+\n from kubernetes import client\n from kubernetes import watch as k8s_watch\n-from table_logger import TableLogger\n+from tabulate import tabulate\n \n from ..constants import constants\n from ..utils import utils\n@@ -27,10 +28,8 @@\n     if namespace is None:\n         namespace = utils.get_default_target_namespace()\n \n-    tbl = TableLogger(\n-        columns='NAME,READY,PREV,LATEST,URL',\n-        colwidth={'NAME': 20, 'READY': 10, 'PREV': 25, 'LATEST': 25, 'URL': 65},\n-        border=False)\n+    headers = ['NAME', 'READY', 'PREV', 'LATEST', 'URL']\n+    table_fmt = 'plain'\n \n     stream = k8s_watch.Watch().stream(\n         client.CustomObjectsApi().list_namespaced_custom_object,\n@@ -63,12 +62,13 @@\n                     for condition in isvc['status'].get('conditions', {}):\n                         if condition.get('type', '') == 'Ready':\n                             status = condition.get('status', 'Unknown')\n-                    tbl(isvc_name, status, 100-traffic_percent, traffic_percent, url)\n+                    print(tabulate([[isvc_name, status, 100 - traffic_percent, traffic_percent, url]],\n+                                   headers=headers, tablefmt=table_fmt))\n                     if status == 'True':\n                         break\n \n             else:\n-                tbl(isvc_name, status, '', '', '')\n+                print(tabulate([[isvc_name, status, '', '', '']], headers=headers, tablefmt=table_fmt))\n                 # Sleep 2 to avoid status section is not generated within a very short time.\n                 time.sleep(2)\n                 continue\n", "issue": "GPL License Violation in the kserve python package\nThe version of the `kserve` package that is currently on PyPI (version `0.10`) violates the GPL license because it depends on [`table-logger`](https://github.com/AleksTk/table-logger), distributed under GPLv2 (you'll see that the library is now MIT, the author updated the license just a few days ago, but hasn't released a new version with the new license yet). No GPLv2 packages should be imported given that `kserve` has an Apache 2 license.\r\n\r\n\r\nThis was recently fixed by this PR https://github.com/kserve/kserve/pull/2673, which accidentally resolved the issue by replacing `table-logger` with `tabulate` (MIT License)\r\n\r\ncc @yuzisun @cliveseldon @jinchihe @ellistarn \r\n\r\nIs it possible to quickly release a patch release `0.10.1` to include the above patch and make sure `kserve` is compliant with the Apache license? As it stands, any distribution and vendor using `kserve` is liable for a license violation.\r\n\r\n\r\n\r\n\n", "before_files": [{"content": "# Copyright 2021 The KServe Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport time\nfrom kubernetes import client\nfrom kubernetes import watch as k8s_watch\nfrom table_logger import TableLogger\n\nfrom ..constants import constants\nfrom ..utils import utils\n\n\ndef isvc_watch(name=None, namespace=None, timeout_seconds=600, generation=0):\n    \"\"\"Watch the created or patched InferenceService in the specified namespace\"\"\"\n\n    if namespace is None:\n        namespace = utils.get_default_target_namespace()\n\n    tbl = TableLogger(\n        columns='NAME,READY,PREV,LATEST,URL',\n        colwidth={'NAME': 20, 'READY': 10, 'PREV': 25, 'LATEST': 25, 'URL': 65},\n        border=False)\n\n    stream = k8s_watch.Watch().stream(\n        client.CustomObjectsApi().list_namespaced_custom_object,\n        constants.KSERVE_GROUP,\n        constants.KSERVE_V1BETA1_VERSION,\n        namespace,\n        constants.KSERVE_PLURAL,\n        timeout_seconds=timeout_seconds)\n\n    for event in stream:\n        isvc = event['object']\n        isvc_name = isvc['metadata']['name']\n        if name and name != isvc_name:\n            continue\n        else:\n            status = 'Unknown'\n            if isvc.get('status', ''):\n                url = isvc['status'].get('url', '')\n                traffic = isvc['status'].get('components', {}).get(\n                    'predictor', {}).get('traffic', [])\n                traffic_percent = 100\n                if constants.OBSERVED_GENERATION in isvc['status']:\n                    observed_generation = isvc['status'][constants.OBSERVED_GENERATION]\n                    for t in traffic:\n                        if t[\"latestRevision\"]:\n                            traffic_percent = t[\"percent\"]\n\n                    if generation != 0 and observed_generation != generation:\n                        continue\n                    for condition in isvc['status'].get('conditions', {}):\n                        if condition.get('type', '') == 'Ready':\n                            status = condition.get('status', 'Unknown')\n                    tbl(isvc_name, status, 100-traffic_percent, traffic_percent, url)\n                    if status == 'True':\n                        break\n\n            else:\n                tbl(isvc_name, status, '', '', '')\n                # Sleep 2 to avoid status section is not generated within a very short time.\n                time.sleep(2)\n                continue\n", "path": "python/kserve/kserve/api/watch.py"}], "after_files": [{"content": "# Copyright 2021 The KServe Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport time\n\nfrom kubernetes import client\nfrom kubernetes import watch as k8s_watch\nfrom tabulate import tabulate\n\nfrom ..constants import constants\nfrom ..utils import utils\n\n\ndef isvc_watch(name=None, namespace=None, timeout_seconds=600, generation=0):\n    \"\"\"Watch the created or patched InferenceService in the specified namespace\"\"\"\n\n    if namespace is None:\n        namespace = utils.get_default_target_namespace()\n\n    headers = ['NAME', 'READY', 'PREV', 'LATEST', 'URL']\n    table_fmt = 'plain'\n\n    stream = k8s_watch.Watch().stream(\n        client.CustomObjectsApi().list_namespaced_custom_object,\n        constants.KSERVE_GROUP,\n        constants.KSERVE_V1BETA1_VERSION,\n        namespace,\n        constants.KSERVE_PLURAL,\n        timeout_seconds=timeout_seconds)\n\n    for event in stream:\n        isvc = event['object']\n        isvc_name = isvc['metadata']['name']\n        if name and name != isvc_name:\n            continue\n        else:\n            status = 'Unknown'\n            if isvc.get('status', ''):\n                url = isvc['status'].get('url', '')\n                traffic = isvc['status'].get('components', {}).get(\n                    'predictor', {}).get('traffic', [])\n                traffic_percent = 100\n                if constants.OBSERVED_GENERATION in isvc['status']:\n                    observed_generation = isvc['status'][constants.OBSERVED_GENERATION]\n                    for t in traffic:\n                        if t[\"latestRevision\"]:\n                            traffic_percent = t[\"percent\"]\n\n                    if generation != 0 and observed_generation != generation:\n                        continue\n                    for condition in isvc['status'].get('conditions', {}):\n                        if condition.get('type', '') == 'Ready':\n                            status = condition.get('status', 'Unknown')\n                    print(tabulate([[isvc_name, status, 100 - traffic_percent, traffic_percent, url]],\n                                   headers=headers, tablefmt=table_fmt))\n                    if status == 'True':\n                        break\n\n            else:\n                print(tabulate([[isvc_name, status, '', '', '']], headers=headers, tablefmt=table_fmt))\n                # Sleep 2 to avoid status section is not generated within a very short time.\n                time.sleep(2)\n                continue\n", "path": "python/kserve/kserve/api/watch.py"}]}