Repository: Morpheus636/zeal-cli (language: python)

Issue:
PIP Packaging Support
- [x] Update necessary files to properly support installation with pip.
- [x] Update the build system and CICD to build and release pip packages on PyPi. 


Discussion / hints:
#47 Suggested pip/pipX, which I agree with, but I don't have much experience with it so I'm not sure what it would entail.

For Linux, I would also suggest FlatPak since it works across most distros and it would be a lot less work than maintaining individual packages for each distro.
Update: Created #55 for package format requests. This thread will be updated with closing requirements for pip packaging.

Fix patch:
diff --git a/.github/workflows/build_binaries.yml b/.github/workflows/build_binaries.yml
index 5b0b930..12ee452 100644
--- a/.github/workflows/build_binaries.yml
+++ b/.github/workflows/build_binaries.yml
@@ -36,7 +36,7 @@ jobs:
       - name: Build Pyinstaller
         run: |
           poetry run python ./build_system/update_version.py
-          poetry run pyinstaller --onefile ./src/zeal_cli.py --name zeal-cli
+          poetry run pyinstaller --onefile ./src/zeal_cli/__main__.py --name zeal-cli
 
       - name: 'Upload Artifact'
         uses: actions/upload-artifact@v3
@@ -71,4 +71,4 @@ jobs:
           files: |
             /home/runner/artifacts/**/zeal-cli
             /home/runner/artifacts/**/zeal-cli.exe
-          fail_on_unmatched_files: true
\ No newline at end of file
+          fail_on_unmatched_files: true
diff --git a/.github/workflows/pypi_build.yml b/.github/workflows/pypi_build.yml
new file mode 100644
index 0000000..561922a
--- /dev/null
+++ b/.github/workflows/pypi_build.yml
@@ -0,0 +1,29 @@
+name: Build for PyPi
+
+on:
+  push:
+    tags:
+      - "v*"
+jobs:
+  tests:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout Code
+        uses: actions/checkout@v2
+
+      - name: Setup Python
+        uses: actions/setup-python@v1
+        with:
+          python-version: 3.10
+
+      - name: Setup Poetry
+        uses: Gr1N/setup-poetry@v7
+
+      - name: Install Packages
+        run: poetry install --no-dev
+
+      - name: Build
+        run: poetry build
+
+      - name: Publish
+        run: poetry publish -u ${{ secrets.PYPI_USER}} -p ${{ secrets.PYPI_PASS}}
diff --git a/build_system/update_version.py b/build_system/update_version.py
index 511b498..797bf5f 100644
--- a/build_system/update_version.py
+++ b/build_system/update_version.py
@@ -5,5 +5,5 @@
 repo = git.Repo(search_parent_directories=True)
 version = repo.git.describe("--tags")
 
-with open(os.path.join(os.getcwd(), "src", "zeal", "version.py"), "w") as file:
+with open(os.path.join(os.getcwd(), "src", "zeal_cli", "zeal", "version.py"), "w") as file:
     file.write(f'build_version = "{version}"\n')
diff --git a/pyproject.toml b/pyproject.toml
index df226fb..a1cde5a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,6 +2,9 @@
 name = "zeal_cli"
 version = "v1.2.0"
 description = ""
+license = "GPL-3.0-or-later"
+readme = "docs/README.md"
+repository = "https://github.com/morpheus636/zeal-cli"
 authors = ["Josh Levin <morpheus636@morpheus636.com>"]
 
 [tool.poetry.dependencies]
diff --git a/src/__init__.py b/src/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/zeal/version.py b/src/zeal/version.py
deleted file mode 100644
index a134754..0000000
--- a/src/zeal/version.py
+++ /dev/null
@@ -1,1 +0,0 @@
-build_version = "RUNNING AS SOURCE"
diff --git a/src/zeal_cli/__init__.py b/src/zeal_cli/__init__.py
new file mode 100644
index 0000000..fbb5c7b
--- /dev/null
+++ b/src/zeal_cli/__init__.py
@@ -0,0 +1,2 @@
+from . import zeal
+from .__main__ import main
diff --git a/src/zeal_cli.py b/src/zeal_cli/__main__.py
similarity index 99%
rename from src/zeal_cli.py
rename to src/zeal_cli/__main__.py
index 8e874f8..a6d947d 100644
--- a/src/zeal_cli.py
+++ b/src/zeal_cli/__main__.py
@@ -2,7 +2,7 @@
 import shutil
 import sys
 
-import zeal
+from . import zeal
 
 
 def main():
diff --git a/src/zeal/__init__.py b/src/zeal_cli/zeal/__init__.py
similarity index 100%
rename from src/zeal/__init__.py
rename to src/zeal_cli/zeal/__init__.py
diff --git a/src/zeal/config.py b/src/zeal_cli/zeal/config.py
similarity index 100%
rename from src/zeal/config.py
rename to src/zeal_cli/zeal/config.py
diff --git a/src/zeal/docset.py b/src/zeal_cli/zeal/docset.py
similarity index 100%
rename from src/zeal/docset.py
rename to src/zeal_cli/zeal/docset.py
diff --git a/src/zeal/downloads.py b/src/zeal_cli/zeal/downloads.py
similarity index 100%
rename from src/zeal/downloads.py
rename to src/zeal_cli/zeal/downloads.py
diff --git a/src/zeal/exceptions.py b/src/zeal_cli/zeal/exceptions.py
similarity index 100%
rename from src/zeal/exceptions.py
rename to src/zeal_cli/zeal/exceptions.py
diff --git a/src/zeal_cli/zeal/version.py b/src/zeal_cli/zeal/version.py
new file mode 100644
index 0000000..bef3410
--- /dev/null
+++ b/src/zeal_cli/zeal/version.py
@@ -0,0 +1,1 @@
+build_version = "v1.2.0-rc1-1-g4d89698"


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 7581f4f..b0f7e08 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -36,4 +36,4 @@ jobs:
       - name: Test Build
         run: |
           poetry run python ./build_system/update_version.py
-          poetry run pyinstaller --onefile ./src/zeal_cli.py --name zeal-cli
\ No newline at end of file
+          poetry run pyinstaller --onefile ./src/zeal_cli/__main__.py --name zeal-cli
diff --git a/tests/conftest.py b/tests/conftest.py
index ace1071..18538e2 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,13 +1,14 @@
-import pytest
 import platform
 from pathlib import Path
 from unittest.mock import patch
 
+import pytest
+
 
 @pytest.fixture(scope="session", autouse=True)
 def mock_winreg():
     if platform.system() == "Windows":
-        with patch("src.zeal.config.config") as patched_obj:
+        with patch("zeal_cli.zeal.config.config") as patched_obj:
             patched_obj._get_docset_dir.return_value = Path(
                 "C:/Users/unittestuser/AppData/Local/Zeal/Zeal/docsets"
             )
diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py
index c71b4cf..d46d47e 100644
--- a/tests/unit/test_config.py
+++ b/tests/unit/test_config.py
@@ -1,15 +1,13 @@
-import sys
-import os
+import platform
 from pathlib import Path
+
 import pytest
-import platform
 
-sys.path.append(os.path.relpath(__file__) + "/../src")
-import src.zeal  # NOQA: E402
+import zeal_cli
 
 
 @pytest.mark.skipif(platform.system() != "Windows", reason="Windows only test")
 def test_config_is_mocked_properly():
-    assert src.zeal.config.config._get_docset_dir() == Path(
+    assert zeal_cli.zeal.config.config._get_docset_dir() == Path(
         "C:/Users/unittestuser/AppData/Local/Zeal/Zeal/docsets"
     )
diff --git a/tests/unit/test_docset.py b/tests/unit/test_docset.py
index 7ffa64a..54c8e0b 100644
--- a/tests/unit/test_docset.py
+++ b/tests/unit/test_docset.py
@@ -1,13 +1,10 @@
 import os
-from pathlib import Path
-import sys
 import tempfile
-import pytest
-
-sys.path.append(os.path.relpath(__file__) + "/../src")
+from pathlib import Path
 
+import pytest
 
-import src.zeal  # NOQA: E402
+import zeal_cli
 
 
 @pytest.fixture(scope="class")
@@ -18,7 +15,7 @@ def setup_temporary_dir():
         os.mkdir(feeds_dir)
         docset_dir = os.path.join(data_dir, "docsets")
         os.mkdir(docset_dir)
-        feeds_path = src.zeal.downloads.get_feeds(feeds_dir)
+        feeds_path = zeal_cli.zeal.downloads.get_feeds(feeds_dir)
         yield (feeds_dir, docset_dir, feeds_path)
 
 
@@ -31,7 +28,7 @@ def test_list_all(
             os.mkdir(os.path.join(docset_dir, "docset2.docset"))
             os.mkdir(os.path.join(docset_dir, "docset3.docset"))
             os.mkdir(os.path.join(docset_dir, "not_a_docset"))
-            docset_list = src.zeal.docset.list_all(docset_dir=Path(docset_dir))
+            docset_list = zeal_cli.zeal.docset.list_all(docset_dir=Path(docset_dir))
             assert "docset1" in docset_list
             assert "docset2" in docset_list
             assert "docset3" in docset_list
@@ -41,34 +38,34 @@ def test_download_docset(self, setup_temporary_dir):
         _, docset_dir, feeds_path = setup_temporary_dir
 
         # Test that the docset is downloaded to the right place
-        src.zeal.docset.download("Django", feeds_path, docset_dir=Path(docset_dir))
+        zeal_cli.zeal.docset.download("Django", feeds_path, docset_dir=Path(docset_dir))
         assert os.path.isdir(os.path.join(docset_dir, "Django.docset"))
-        with pytest.raises(src.zeal.exceptions.DocsetAlreadyInstalledError):
-            src.zeal.docset.download("Django", feeds_path, docset_dir=Path(docset_dir))
+        with pytest.raises(zeal_cli.zeal.exceptions.DocsetAlreadyInstalledError):
+            zeal_cli.zeal.docset.download("Django", feeds_path, docset_dir=Path(docset_dir))
 
     def test_delete_docset(self, setup_temporary_dir):
         _, docset_dir, feeds_path = setup_temporary_dir
 
         # Test that the docset not existing raises an exception
-        with pytest.raises(src.zeal.exceptions.DocsetNotInstalledError):
-            src.zeal.docset.remove("TotallyRealDocset", docset_dir=Path(docset_dir))
+        with pytest.raises(zeal_cli.zeal.exceptions.DocsetNotInstalledError):
+            zeal_cli.zeal.docset.remove("TotallyRealDocset", docset_dir=Path(docset_dir))
         # Test that a file with the same name still raises the exception
         with open(os.path.join(docset_dir, "TotallyRealDocset.docset"), "a"):
             pass
-        with pytest.raises(src.zeal.exceptions.DocsetNotInstalledError):
-            src.zeal.docset.remove("TotallyRealDocset", docset_dir=Path(docset_dir))
+        with pytest.raises(zeal_cli.zeal.exceptions.DocsetNotInstalledError):
+            zeal_cli.zeal.docset.remove("TotallyRealDocset", docset_dir=Path(docset_dir))
         os.remove(os.path.join(docset_dir, "TotallyRealDocset.docset"))
 
         # Test that the docset gets removed when it exists.
         os.mkdir(os.path.join(docset_dir, "TotallyRealDocset.docset"))
-        src.zeal.docset.remove("TotallyRealDocset", docset_dir=Path(docset_dir))
+        zeal_cli.zeal.docset.remove("TotallyRealDocset", docset_dir=Path(docset_dir))
         assert not os.path.isdir(os.path.join(docset_dir, "TotallyRealDocset.docset"))
 
     def test_list_docset_version(self, setup_temporary_dir):
         _, docset_dir, feeds_path = setup_temporary_dir
 
         # Test that for a docset a list of version strings is returned
-        docset_versions = src.zeal.docset.get_docset_versions("Django", feeds_path)
+        docset_versions = zeal_cli.zeal.docset.get_docset_versions("Django", feeds_path)
         assert isinstance(docset_versions, list)
         assert all(isinstance(version, str) for version in docset_versions)
 
@@ -78,11 +75,11 @@ def test_download_docset_by_version(self, setup_temporary_dir):
         _, docset_dir, feeds_path = setup_temporary_dir
 
         # Test that the docset is downloaded to the right place
-        src.zeal.docset.download(
+        zeal_cli.zeal.docset.download(
             "Django", feeds_path, docset_version="2.2.7", docset_dir=Path(docset_dir)
         )
         assert os.path.isdir(os.path.join(docset_dir, "Django.docset"))
-        with pytest.raises(src.zeal.exceptions.DocsetAlreadyInstalledError):
-            src.zeal.docset.download(
+        with pytest.raises(zeal_cli.zeal.exceptions.DocsetAlreadyInstalledError):
+            zeal_cli.zeal.docset.download(
                 "Django", feeds_path, docset_version="2.2.7", docset_dir=Path(docset_dir)
             )
diff --git a/tests/unit/test_downloads.py b/tests/unit/test_downloads.py
index 885a1d2..d77b934 100644
--- a/tests/unit/test_downloads.py
+++ b/tests/unit/test_downloads.py
@@ -1,15 +1,12 @@
 import os
-import sys
 import tempfile
 
-
-sys.path.append(os.path.relpath(__file__) + "/../src")
-import src.zeal  # NOQA: E402
+import zeal_cli
 
 
 def test_get_feeds():
     with tempfile.TemporaryDirectory() as data_dir:
-        feeds_path = src.zeal.downloads.get_feeds(data_dir=data_dir)
+        feeds_path = zeal_cli.zeal.downloads.get_feeds(data_dir=data_dir)
         assert os.path.isdir(feeds_path)
         assert os.path.isfile(os.path.join(feeds_path, "Django.xml"))
         assert os.path.isfile(os.path.join(feeds_path, "Python_3.xml"))