From 31fc3d2be54e5f8072f398648e518b3ecfe2317c Mon Sep 17 00:00:00 2001 From: Vlad Stirbu Date: Tue, 5 Nov 2024 22:27:56 +0200 Subject: [PATCH 1/7] refactor: rename package from q8s_kernel to q8s and update dependencies --- pyproject.toml | 14 ++--- {q8s_kernel => q8s}/__init__.py | 0 {q8s_kernel => q8s}/__main__.py | 0 {q8s_kernel => q8s}/cli.py | 20 ++++++- {q8s_kernel => q8s}/deps/__init__.py | 0 {q8s_kernel => q8s}/deps/code_analyzer.py | 0 {q8s_kernel => q8s}/deps/parser.py | 0 {q8s_kernel => q8s}/execution.py | 0 q8s/install.py | 73 +++++++++++++++++++++++ {q8s_kernel => q8s}/k8s.py | 0 {q8s_kernel => q8s}/kernel.py | 8 ++- {q8s_kernel => q8s}/testbook.py | 2 +- 12 files changed, 104 insertions(+), 13 deletions(-) rename {q8s_kernel => q8s}/__init__.py (100%) rename {q8s_kernel => q8s}/__main__.py (100%) rename {q8s_kernel => q8s}/cli.py (77%) rename {q8s_kernel => q8s}/deps/__init__.py (100%) rename {q8s_kernel => q8s}/deps/code_analyzer.py (100%) rename {q8s_kernel => q8s}/deps/parser.py (100%) rename {q8s_kernel => q8s}/execution.py (100%) create mode 100644 q8s/install.py rename {q8s_kernel => q8s}/k8s.py (100%) rename {q8s_kernel => q8s}/kernel.py (91%) rename {q8s_kernel => q8s}/testbook.py (98%) diff --git a/pyproject.toml b/pyproject.toml index e7c84e5..e15ab95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "q8s_kernel" +name = "q8s" version = "0.1.0" description = "Kernel extension for executing quantum programs in simulators on q8s clusters" readme = "README.md" @@ -10,16 +10,16 @@ classifiers = [ "Intended Audience :: Developers", ] dependencies = [ - "stdlib-list==0.10.0", - "ipython==8.17.2", + # "stdlib-list==0.10.0", + # "ipython==8.17.2", "ipykernel==6.26.0", "kubernetes==29.0.0", - "notebook==7.0.6", - "python_on_whales==0.70.0", + # "notebook==7.0.6", + # "python_on_whales==0.70.0", "python-dotenv==1.0.1", "typer==0.12.3", ] -requires-python = ">= 3.8" +# requires-python = ">= 3.8" [build-system] requires = ["setuptools >= 61.0"] @@ -34,4 +34,4 @@ benchmark = ["pandas", "testbook", "mlflow"] development = ["pyre-check"] [project.scripts] -q8sctl = "q8s_kernel.cli:app" +q8sctl = "q8s.cli:app" diff --git a/q8s_kernel/__init__.py b/q8s/__init__.py similarity index 100% rename from q8s_kernel/__init__.py rename to q8s/__init__.py diff --git a/q8s_kernel/__main__.py b/q8s/__main__.py similarity index 100% rename from q8s_kernel/__main__.py rename to q8s/__main__.py diff --git a/q8s_kernel/cli.py b/q8s/cli.py similarity index 77% rename from q8s_kernel/cli.py rename to q8s/cli.py index c100677..0b47001 100644 --- a/q8s_kernel/cli.py +++ b/q8s/cli.py @@ -1,9 +1,10 @@ import os from pathlib import Path import typer +import sys from typing_extensions import Annotated -from kubernetes import config -from q8s_kernel.execution import K8sContext +from q8s.execution import K8sContext +from q8s.install import install_my_kernel_spec app = typer.Typer() @@ -53,5 +54,20 @@ def execute( print(f"output stream: {stream_name}") +@app.command() +def jupyter( + install: Annotated[ + bool, + typer.Option( + help="Install kernel spec for Jupyter", + ), + ] = False, +): + print("install:", install) + if install: + install_my_kernel_spec(user=False, prefix=sys.prefix) + # install_my_kernel_spec(user=user, prefix=prefix) + + # if __name__ == "__main__": app() diff --git a/q8s_kernel/deps/__init__.py b/q8s/deps/__init__.py similarity index 100% rename from q8s_kernel/deps/__init__.py rename to q8s/deps/__init__.py diff --git a/q8s_kernel/deps/code_analyzer.py b/q8s/deps/code_analyzer.py similarity index 100% rename from q8s_kernel/deps/code_analyzer.py rename to q8s/deps/code_analyzer.py diff --git a/q8s_kernel/deps/parser.py b/q8s/deps/parser.py similarity index 100% rename from q8s_kernel/deps/parser.py rename to q8s/deps/parser.py diff --git a/q8s_kernel/execution.py b/q8s/execution.py similarity index 100% rename from q8s_kernel/execution.py rename to q8s/execution.py diff --git a/q8s/install.py b/q8s/install.py new file mode 100644 index 0000000..768ed25 --- /dev/null +++ b/q8s/install.py @@ -0,0 +1,73 @@ +import json +import os +import sys +import argparse +import pathlib +import shutil + +from jupyter_client.kernelspec import KernelSpecManager +from IPython.utils.tempdir import TemporaryDirectory + +# from .resources import _ICON_PATH + +kernel_json = { + "argv": [sys.executable, "-m", "q8s", "-f", "{connection_file}"], + "display_name": "Q8s kernel", + "language": "python", +} + + +def install_my_kernel_spec(user=True, prefix=None): + with TemporaryDirectory() as td: + os.chmod(td, 0o755) # Starts off as 700, not user readable + with open(os.path.join(td, "kernel.json"), "w") as f: + json.dump(kernel_json, f, sort_keys=True) + # shutil.copyfile(_ICON_PATH, pathlib.Path(td) / _ICON_PATH.name) + print("Installing q8s kernel spec") + KernelSpecManager().install_kernel_spec(td, "q8s", user=user, prefix=prefix) + + +def _is_root(): + try: + return os.geteuid() == 0 + except AttributeError: + return False # assume not an admin on non-Unix platforms + + +def main(argv=None): + parser = argparse.ArgumentParser( + description="Install KernelSpec for Qubernetes Kernel" + ) + prefix_locations = parser.add_mutually_exclusive_group() + + prefix_locations.add_argument( + "--user", + help="Install KernelSpec in user's home directory", + action="store_true", + ) + prefix_locations.add_argument( + "--sys-prefix", + help="Install KernelSpec in sys.prefix. Useful in conda / virtualenv", + action="store_true", + dest="sys_prefix", + ) + prefix_locations.add_argument( + "--prefix", help="Install KernelSpec in this prefix", default=None + ) + + args = parser.parse_args(argv) + + user = False + prefix = None + if args.sys_prefix: + prefix = sys.prefix + elif args.prefix: + prefix = args.prefix + elif args.user or not _is_root(): + user = True + + install_my_kernel_spec(user=user, prefix=prefix) + + +if __name__ == "__main__": + main() diff --git a/q8s_kernel/k8s.py b/q8s/k8s.py similarity index 100% rename from q8s_kernel/k8s.py rename to q8s/k8s.py diff --git a/q8s_kernel/kernel.py b/q8s/kernel.py similarity index 91% rename from q8s_kernel/kernel.py rename to q8s/kernel.py index 08d977c..bd00e0e 100644 --- a/q8s_kernel/kernel.py +++ b/q8s/kernel.py @@ -2,7 +2,7 @@ from ipykernel.kernelbase import Kernel import logging -from q8s_kernel.execution import K8sContext +from q8s.execution import K8sContext USE_KUBERNETES = True FORMAT = "[%(levelname)s %(asctime)-15s q8s_kernel] %(message)s" @@ -18,8 +18,10 @@ class Q8sKernel(Kernel): language_version = "0.1" language_info = { "name": "Any text", - "mimetype": "text/plain", - "file_extension": ".txt", + "mimetype": "text/x-python", + "pygments_lexer": "ipython%d" % 3, + "file_extension": ".py", + "nbconvert_exporter": "python", } banner = "q8s" diff --git a/q8s_kernel/testbook.py b/q8s/testbook.py similarity index 98% rename from q8s_kernel/testbook.py rename to q8s/testbook.py index 67fed37..38a93cf 100644 --- a/q8s_kernel/testbook.py +++ b/q8s/testbook.py @@ -3,7 +3,7 @@ from ipykernel.kernelbase import Kernel import logging -from q8s_kernel.kernel import Q8sKernel +from q8s.kernel import Q8sKernel from .k8s import execute From 6d396b8acc7e4fa137bce83c75b69fe409b9c20f Mon Sep 17 00:00:00 2001 From: Vlad Stirbu Date: Wed, 6 Nov 2024 07:15:41 +0200 Subject: [PATCH 2/7] feat: add GitHub Actions workflow for publishing to PyPI and TestPyPI --- .github/workflows/publish.yml | 117 ++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..72d7b7a --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,117 @@ +name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI + +on: push + +jobs: + build: + name: Build distribution 📦 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish-to-pypi: + name: >- + Publish Python 🐍 distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/q8s # Replace with your PyPI project name + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + github-release: + name: >- + Sign the Python 🐍 distribution 📦 with Sigstore + and upload them to GitHub Release + needs: + - publish-to-pypi + runs-on: ubuntu-latest + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v3.0.0 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ github.ref_name }}' + --repo '${{ github.repository }}' + --notes "" + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + '${{ github.ref_name }}' dist/** + --repo '${{ github.repository }}' + + publish-to-testpypi: + name: Publish Python 🐍 distribution 📦 to TestPyPI + needs: + - build + runs-on: ubuntu-latest + + environment: + name: testpypi + url: https://test.pypi.org/p/q8s + + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ From c3ced12114386255ff82f65dabd4f92ad3a7236c Mon Sep 17 00:00:00 2001 From: Vlad Stirbu Date: Wed, 6 Nov 2024 07:42:14 +0200 Subject: [PATCH 3/7] chore: update pyproject.toml with author info, additional classifiers, and project URLs --- pyproject.toml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e15ab95..265f1ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,12 +2,16 @@ name = "q8s" version = "0.1.0" description = "Kernel extension for executing quantum programs in simulators on q8s clusters" +authors = [{ name = "Vlad Stirbu", email = "vstirbu@gmail.com" }] readme = "README.md" license = { file = "LICENSE" } keywords = ["jupyter", "quantum", "kernel"] classifiers = [ "Development status :: 3 - Alpha", "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", ] dependencies = [ # "stdlib-list==0.10.0", @@ -19,7 +23,11 @@ dependencies = [ "python-dotenv==1.0.1", "typer==0.12.3", ] -# requires-python = ">= 3.8" +requires-python = ">= 3.8" + +[project.urls] +Homepage = "https://github.com/torqs-project/q8s-kernel" +Issues = "https://github.com/torqs-project/q8s-kernel/issues" [build-system] requires = ["setuptools >= 61.0"] From 8ca2e69231b9089cf5e6799590612a4dcf0b6d54 Mon Sep 17 00:00:00 2001 From: Vlad Stirbu Date: Wed, 6 Nov 2024 07:54:57 +0200 Subject: [PATCH 4/7] chore: update GitHub Actions workflow to skip existing packages during publish --- .github/workflows/publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 72d7b7a..df723b2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -115,3 +115,4 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ + skip-existing: true From 100f6fb6ee7f3dc785abebba40cdb28935c0078f Mon Sep 17 00:00:00 2001 From: Vlad Stirbu Date: Wed, 6 Nov 2024 07:56:38 +0200 Subject: [PATCH 5/7] chore: update GitHub Actions workflow to enable verbose output during publish --- .github/workflows/publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index df723b2..0191983 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -116,3 +116,4 @@ jobs: with: repository-url: https://test.pypi.org/legacy/ skip-existing: true + verbose: true From ea19487d92c0fddcf3ddbaa99fe6b27ec0c6c077 Mon Sep 17 00:00:00 2001 From: Vlad Stirbu Date: Wed, 6 Nov 2024 08:00:11 +0200 Subject: [PATCH 6/7] chore: update development status classifier from Alpha to Beta in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 265f1ef..50db26e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" license = { file = "LICENSE" } keywords = ["jupyter", "quantum", "kernel"] classifiers = [ - "Development status :: 3 - Alpha", + "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", From 1b5d553a6e04d4a43cf36012029d1dbf45a1adb5 Mon Sep 17 00:00:00 2001 From: Vlad Stirbu Date: Wed, 6 Nov 2024 08:18:52 +0200 Subject: [PATCH 7/7] docs: update README to reflect project name change and usage instructions --- README.md | 55 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index eea5a65..7a082b3 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,37 @@ -# q8s-kernel +# q8s -Kernel extension for executing quantum programs in simulators on q8s clusters +Toolset for executing quantum jobs on [Qubernetes](https://www.qubernetes.dev). ## Installation Install the for project folder: ```bash -pip install . +pip install q8s ``` -## Development - -### Prerequisites - -The development environment requires the following tools to be installed: - -- [Docker](https://www.docker.com/get-started) -- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) +## Usage -### Setup +### CLI -Install dependencies: +Sumbit a job to the Qubernetes cluster: ```bash -pip install -e . +q8sctl execute app.py --kubeconfig /path/to/kubeconfig ``` -The jupyter kernel needs to be installed locally for jupyter notebook to find it. To install the q8s-kernel when using a virtual environment, run the following command: +For more options, run: ```bash -jupyter kernelspec install . --name=q8s-kernel --sys-prefix +q8sctl execute --help ``` -otherwise, run the following command: +### Jupyter Notebook + +Install the `q8s-kernel`: ```bash -jupyter kernelspec install . --name=q8s-kernel --user +q8sctl jupyter --install ``` Start the jupyter notebook server: @@ -50,3 +45,27 @@ or the jupyter lab server: ```bash jupyter lab ``` + +Select the `Q8s kernel` when creating a new notebook. + +## Development + +### Prerequisites + +The development environment requires the following tools to be installed: + +- [Docker](https://www.docker.com/get-started) + +### Setup + +Install the project in editable mode: + +```bash +pip install -e . +``` + +If the project is installed in a virtual environment, the `q8s-kernel` can be installed by running the following command: + +```bash +q8sctl jupyter --install +```