diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index 78dbaf5..0000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: Check and Build -on: - push: - branches: - - main - pull_request: - workflow_dispatch: - release: - types: [released] -permissions: read-all -jobs: - build: - name: Make pip packages - runs-on: ubuntu-latest - steps: - - name: Checkout the Git repository - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - cache: 'pip' - - name: Install build tool - run: pip install build - - name: Building package - run: python -m build - - uses: actions/upload-artifact@v4 - with: - name: pip - path: dist/* - if-no-files-found: error - publish: - name: Publish to PyPi - runs-on: ubuntu-latest - needs: [build] - if: github.repository == 'Eeems-Org/python-launcherctl' && github.event_name == 'release' && startsWith(github.ref, 'refs/tags') - permissions: - id-token: write - environment: - name: pypi - url: https://pypi.org/p/launcherctl - steps: - - name: Download pip packages - id: download - uses: actions/download-artifact@v4 - with: - name: pip - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: ${{ steps.download.outputs.download-path }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..4040fef --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,151 @@ +name: Check and Build + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + release: + types: + - released + +permissions: read-all + +jobs: + lint: + name: Lint codebase + runs-on: ubuntu-latest + strategy: + matrix: + python: &python-versions + - "3.11" + - "3.12" + - "3.13" + - "3.14" + steps: + - name: Checkout the Git repository + uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python }} + cache: pip + - &install-emake + name: Install emake + run: pip install emake + - name: Run lint + run: emake lint + + build-sdist: + name: Build sdist + needs: &build-needs + - lint + runs-on: ubuntu-latest + steps: + - name: Checkout the Git repository + uses: actions/checkout@v6 + - *install-emake + - name: Building sdist + run: emake build --sdist + - uses: actions/upload-artifact@v6 + with: + name: pip-sdist + path: dist/* + if-no-files-found: error + + build-any-wheel: + name: Build wheel + needs: *build-needs + runs-on: ubuntu-latest + steps: + - name: Checkout the Git repository + uses: actions/checkout@v6 + - *install-emake + - name: Building wheel + run: emake build --wheel + - uses: actions/upload-artifact@v6 + with: + name: pip-wheel-none-any + path: dist/* + if-no-files-found: error + + build-wheel: + name: Build wheel + needs: *build-needs + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python: *python-versions + arch: + - aarch64 + - armv7l + libc: + - glibc + steps: + - name: Checkout the Git repository + uses: actions/checkout@v6 + - *install-emake + - name: Building wheel + run: | + emake build \ + --native-wheel \ + --arch ${{ matrix.arch }} \ + --libc ${{ matrix.libc }} \ + --python ${{ matrix.python }} + - uses: actions/upload-artifact@v6 + with: + name: pip-wheel-${{ matrix.python }}-${{ matrix.arch }}-${{ matrix.libc }} + path: dist/* + if-no-files-found: error + + publish: + name: Publish to PyPi + if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') + needs: &release-needs + - build-sdist + - build-any-wheel + - build-wheel + runs-on: ubuntu-latest + permissions: + id-token: write + environment: + name: pypi + url: https://pypi.org/p/launcherctl + steps: + - name: Download pip packages + id: download + uses: actions/download-artifact@v8 + with: + pattern: pip-* + merge-multiple: true + path: dist + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: ${{ steps.download.outputs.download-path }} + skip-existing: true + + release: + name: Add release artifacts + if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') + needs: *release-needs + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout the Git repository + uses: actions/checkout@v6 + - name: Download artifact + id: download + uses: actions/download-artifact@v8 + with: + pattern: pip-* + merge-multiple: true + path: dist + - name: Upload to release + run: find . -type f | xargs -rI {} gh release upload "$TAG" {} --clobber + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.event.release.tag_name }} + working-directory: ${{ steps.download.outputs.download-path }} diff --git a/Makefile b/Makefile index 9022369..691097d 100644 --- a/Makefile +++ b/Makefile @@ -15,16 +15,8 @@ pip install --force-reinstall /tmp/launcherctl-${VERSION}-py3-none-any.whl endef export SCRIPT -ifeq ($(VENV_BIN_ACTIVATE),) -VENV_BIN_ACTIVATE := .venv/bin/activate -endif - - -dist/launcherctl-${VERSION}.tar.gz: $(shell find launcherctl -type f) - python -m build --sdist - dist/launcherctl-${VERSION}-py3-none-any.whl: $(shell find launcherctl -type f) - python -m build --wheel + emake build --wheel clean: git clean --force -dX @@ -40,26 +32,4 @@ test: install | ssh root@10.11.99.1 \ "bash -ec 'PATH=${PATH} /opt/bin/python -u'" -$(VENV_BIN_ACTIVATE): - @echo "Setting up development virtual env in .venv" - python -m venv .venv - . $(VENV_BIN_ACTIVATE); \ - python -m pip install ruff - -lint: $(VENV_BIN_ACTIVATE) - . $(VENV_BIN_ACTIVATE); \ - python -m ruff check - -lint-fix: $(VENV_BIN_ACTIVATE) - . $(VENV_BIN_ACTIVATE); \ - python -m ruff check - -format: $(VENV_BIN_ACTIVATE) - . $(VENV_BIN_ACTIVATE); \ - python -m ruff format --diff - -format-fix: $(VENV_BIN_ACTIVATE) - . $(VENV_BIN_ACTIVATE); \ - python -m ruff format - -.PHONY: clean install test deploy lint lint-fix format format-fix +.PHONY: clean install test deploy diff --git a/launcherctl/_app.py b/launcherctl/_app.py index 6661c83..fd8fb16 100644 --- a/launcherctl/_app.py +++ b/launcherctl/_app.py @@ -2,8 +2,8 @@ class App: - def __init__(self, name: str): - self.name = name + def __init__(self, name: str) -> None: + self.name: str = name @property def is_running(self) -> bool: @@ -14,21 +14,21 @@ def is_paused(self) -> bool: return self.name in api.paused.keys() def start(self) -> None: - launcherctl("start-app", self.name) + _ = launcherctl("start-app", self.name) def stop(self) -> None: - launcherctl("stop-app", self.name) + _ = launcherctl("stop-app", self.name) def pause(self) -> None: - launcherctl("pause-app", self.name) + _ = launcherctl("pause-app", self.name) def resume(self) -> None: - launcherctl("resume-app", self.name) + _ = launcherctl("resume-app", self.name) class API: def keys(self) -> list[str]: - return [x.decode("utf-8") for x in launcherctl("list-apps").splitlines()] + return launcherctl("list-apps").splitlines() def __contains__(self, key: str) -> bool: return key in self.keys() @@ -40,22 +40,12 @@ def __getitem__(self, key: str) -> App: return App(key) @property - def running(self) -> dict[App]: - return { - x: App(x) - for x in [ - x.decode("utf-8") for x in launcherctl("list-running-apps").splitlines() - ] - } + def running(self) -> dict[str, App]: + return {x: App(x) for x in launcherctl("list-running-apps").splitlines()} @property - def paused(self) -> dict[App]: - return { - x: App(x) - for x in [ - x.decode("utf-8") for x in launcherctl("list-paused-apps").splitlines() - ] - } + def paused(self) -> dict[str, App]: + return {x: App(x) for x in launcherctl("list-paused-apps").splitlines()} api = API() diff --git a/launcherctl/_launcher.py b/launcherctl/_launcher.py index d12ad4f..b44e167 100644 --- a/launcherctl/_launcher.py +++ b/launcherctl/_launcher.py @@ -1,30 +1,28 @@ import subprocess +from collections.abc import Callable -from typing import Callable - -from ._app import App from ._util import launcherctl class Launcher: - def __init__(self, name: str): - self.name = name + def __init__(self, name: str) -> None: + self.name: str = name - def logs(self, onlogline: Callable[[str], None] = None) -> list[str] | None: - pass + def logs(self, _onlogline: Callable[[str], None] | None = None) -> list[str] | None: + raise NotImplementedError() - def start(self): - launcherctl("start-launcher", self.name) + def start(self) -> None: + _ = launcherctl("start-launcher", self.name) - def stop(self): - launcherctl("stop-launcher", self.name) + def stop(self) -> None: + _ = launcherctl("stop-launcher", self.name) - def enable(self, start: bool = False): + def enable(self, start: bool = False) -> None: if start: - launcherctl("switch-launcher", "--start", self.name) + _ = launcherctl("switch-launcher", "--start", self.name) else: - launcherctl("switch-launcher", self.name) + _ = launcherctl("switch-launcher", self.name) @property def is_current(self) -> bool: @@ -50,23 +48,23 @@ def is_active(self) -> bool: class API: def keys(self) -> list[str]: - return [x.decode("utf-8") for x in launcherctl("list-launchers").splitlines()] + return launcherctl("list-launchers").splitlines() def __contains__(self, key: str) -> bool: return key in self.keys() - def __getitem__(self, key: str) -> App: + def __getitem__(self, key: str) -> Launcher: if key not in self: raise KeyError() return Launcher(key) @property - def current(self): - return Launcher(launcherctl("status").splitlines()[0][14:-4].decode("utf-8")) + def current(self) -> Launcher: + return Launcher(launcherctl("status").splitlines()[0][14:-4]) - def switch(launcher: Launcher | str, start: bool = False): - if not isinstance(Launcher, launcher): + def switch(self, launcher: Launcher | str, start: bool = False) -> None: + if not isinstance(launcher, Launcher): launcher = Launcher(launcher) launcher.enable(start) diff --git a/launcherctl/_util.py b/launcherctl/_util.py index d2e8a00..a86a2f4 100644 --- a/launcherctl/_util.py +++ b/launcherctl/_util.py @@ -1,13 +1,15 @@ import subprocess -def LauncherCtlException(Exception): +class LauncherCtlException(Exception): pass -def launcherctl(*args) -> str | None: - try: - return subprocess.check_output(["/opt/bin/launcherctl"] + list(args)) - except subprocess.CalledProcessError as e: - stdout = e.output.decode("utf-8") - raise LauncherCtlException(stdout) from e +def launcherctl(*args: str) -> str: + proc = subprocess.run( + ["/opt/bin/launcherctl", *args], text=True, check=False, capture_output=True + ) + if not proc.returncode: + return proc.stdout + + raise LauncherCtlException(f"{proc.stdout}\n{proc.stderr}") diff --git a/pyproject.toml b/pyproject.toml index cc46bd7..8d29664 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,8 @@ name = "launcherctl" description = "Python wrapper around launcherctl." readme = "README.md" -version = "1.0.1" +license = "MIT" +version = "1.0.2" requires-python = ">= 3.11" authors = [ {name = "Nathaniel van Diepen", email = "eeems@eeems.email"}, @@ -13,16 +14,50 @@ maintainers = [ classifiers = [ "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] +[project.optional-dependencies] +test = ["pytest"] + [project.urls] Homepage = "https://github.com/Eeems-Org/python-launcherctl" Repository = "https://github.com/Eeems-Org/python-launcherctl.git" Issues = "https://github.com/Eeems-Org/python-launcherctl/issues" [build-system] -requires = ["setuptools >= 65.0"] -build-backend = "setuptools.build_meta" +requires = ["setuptools>=77.0", "nuitka>=4.0.6"] +build-backend = "nuitka.distutils.Build" + +[tool.ruff] +exclude = [".venv", "build"] + +[tool.ruff.lint] +extend-select = [ + "UP", + "PL", + "ANN", + "S", +] +ignore = [ + "PLW0603", + "PLR2004", + "PLR0915", + "PLR0912", + "PLR0911", + "PLR6301", + "PLR0913", + "S101", + "S404", + "S603", + "S607", + "ANN401", + "ANN001", + "ANN003", +] + +[tool.pyright] +exclude = [".venv", "build"]