diff --git a/.flake8 b/.flake8 index af9bc26b..713d0e3d 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,8 @@ [flake8] -exclude = resources_rc.py,.venv,main_window.py +exclude = + resources_rc.py, + .venv, + main_window.py + build, + dist +max-line-length = 90 \ No newline at end of file diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml new file mode 100644 index 00000000..f76c5857 --- /dev/null +++ b/.github/workflows/build-and-release.yml @@ -0,0 +1,80 @@ +name: Build and Release Yin-Yang + +on: + push: + pull_request: + +jobs: + build: + name: "Build application as Whl" + strategy: + fail-fast: false + matrix: + python-version: ["3.12"] + os: [ubuntu-22.04] + runs-on: ${{matrix.os}} + steps: + # Checkout repo and set up python + - uses: actions/checkout@v4 + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: ${{matrix.python-version}} + # Install and configure poetry + - name: Install Poetry + uses: abatilo/actions-poetry@v2 + - name: Set up local virtual environment + run: | + poetry config virtualenvs.create true --local + poetry config virtualenvs.in-project true --local + # Load cached venv if it exists + - name: Cache packages + id: cached-poetry-dependencies + uses: actions/cache@v4 + with: + # This path is specific to ubuntu + path: ./.venv + key: venv-${{ hashFiles('poetry.lock') }} + # Install dependencies of cache does not exist + - name: Install system dependencies + run: | + sudo apt install qt6-base-dev libsystemd-dev gcc + - name: Install Poetry dependencies + # if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + run: | + poetry install --sync --no-interaction + # Compile and build Yin-Yang + - name: Compile ui, translations and resources + run: poetry run ./scripts/build_ui.sh + - name: Build Whl for release + run: poetry build -f wheel -n -o . + # Upload build artifacts for later use + - name: Upload yin-yang whl for flatpak build + uses: actions/upload-artifact@v4 + with: + name: yin-yang-${{ github.sha }}-py3-none-any.whl + path: '*.whl' + + flatpak: + name: "Build flatpak file" + runs-on: ubuntu-22.04 + needs: build + container: + image: bilelmoussaoui/flatpak-github-actions:kde-5.15-23.08 + options: --privileged + strategy: + matrix: + arch: [x86_64] + steps: + - uses: actions/checkout@v4 + - name: Download build from last step + uses: actions/download-artifact@v4 + with: + path: dist/ + name: yin-yang-${{ github.sha }}-py3-none-any.whl + - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: yin-yang.flatpak + manifest-path: sh.oskar.yin-yang.json + cache-key: flatpak-builder-${{ github.sha }} + arch: x86_64 \ No newline at end of file diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-ci.yml similarity index 77% rename from .github/workflows/python-app.yml rename to .github/workflows/python-ci.yml index 6c615b52..0a3c15a0 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-ci.yml @@ -17,27 +17,36 @@ jobs: os: [ubuntu-22.04] runs-on: ${{matrix.os}} steps: + # Checkout repo and set up python - uses: actions/checkout@v4 - name: Install Python uses: actions/setup-python@v5 with: python-version: ${{matrix.python-version}} + # Install and configure poetry - name: Install Poetry uses: abatilo/actions-poetry@v2 - name: Set up local virtual environment run: | poetry config virtualenvs.create true --local poetry config virtualenvs.in-project true --local + # Load cached venv if it exists - name: Cache packages + id: cached-poetry-dependencies uses: actions/cache@v4 with: # This path is specific to ubuntu path: ./.venv key: venv-${{ hashFiles('poetry.lock') }} - - name: Install dependencies + # Install dependencies of cache does not exist + - name: Install system dependencies run: | sudo apt install qt6-base-dev libsystemd-dev gcc - poetry install + - name: Install Poetry dependencies + # if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + run: | + poetry install --sync --no-interaction + # Build and test Yin-Yang - name: Compile ui, translations and resources run: poetry run ./scripts/build_ui.sh - name: Lint with flake8 diff --git a/.gitignore b/.gitignore index 4990b28b..6898446e 100755 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,19 @@ +# Created by https://www.toptal.com/developers/gitignore/api/flatpak,python,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=flatpak,python,visualstudiocode +*~ __pycache__ build-test* build-ui-* yin_yang/build.py -c_cpp_properties.json +setup.py -# Taken from https://github.com/github/gitignore/blob/main/Python.gitignore -# 12/18/2023 +### Flatpak ### +.flatpak-builder +build +build-dir +repo +### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -167,3 +174,34 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/flatpak,python,visualstudiocode diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..e749d6bf --- /dev/null +++ b/.pylintrc @@ -0,0 +1 @@ +extension-pkg-whitelist=PyQt5 \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..18c078ae --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,25 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Yin-Yang", + "type": "debugpy", + "request": "launch", + "console": "integratedTerminal", + "module": "yin_yang", + "justMyCode": true + }, + { + "name": "Debug Poetry Generator", + "type": "debugpy", + "request": "launch", + "console": "integratedTerminal", + "program": "flatpak-poetry-generator.py", + "justMyCode": true, + "args": ["poetry.lock"] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a73a41b..be93e3d9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1,10 @@ { -} \ No newline at end of file + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "black-formatter.args": [ + "--skip-string-normalization" + ] +} diff --git a/flatpak-poetry-generator.py b/flatpak-poetry-generator.py new file mode 100644 index 00000000..59956fc4 --- /dev/null +++ b/flatpak-poetry-generator.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 + +""" +This file is a modified version of the file available here: +https://github.com/flatpak/flatpak-builder-tools/blob/master/poetry/flatpak-poetry-generator.py + +The file above has a long-standing issue pulling dependencies for Pyside6. This modified version +is hard-coded to work with Pyside6. Hopefully there is a better long-term fix in the future. + +Chase Christiansen, 04/13/2024 +""" +__license__ = "MIT" + +import argparse +import json +import re +import sys +import urllib.parse +import urllib.request +from collections import OrderedDict + +import toml + + +def get_pypi_source(name: str, version: str, hashes: list) -> tuple: + """Get the source information for a dependency. + + Args: + name (str): The package name. + version (str): The package version. + hashes (list): The list of hashes for the package version. + + Returns (tuple): The url and sha256 hash. + + """ + url = "https://pypi.org/pypi/{}/json".format(name) + print("Extracting download url and hash for {}, version {}".format(name, version)) + with urllib.request.urlopen(url) as response: + body = json.loads(response.read().decode("utf-8")) + for release, source_list in body["releases"].items(): + if release == version: + for source in source_list: + if ( + name == "pyside6-addons" + or name == "pyside6-essentials" + or name == "shiboken6" + ): + if ( + source["filename"].endswith("x86_64.whl") + and "manylinux" in source["filename"] + ): + return source["url"], source["digests"]["sha256"] + if ( + source["packagetype"] == "bdist_wheel" + and "py3" in source["python_version"] + and source["digests"]["sha256"] in hashes + ): + return source["url"], source["digests"]["sha256"] + for source in source_list: + if ( + source["packagetype"] == "sdist" + and "source" in source["python_version"] + and source["digests"]["sha256"] in hashes + ): + return source["url"], source["digests"]["sha256"] + else: + raise Exception("Failed to extract url and hash from {}".format(url)) + + +def get_module_sources(parsed_lockfile: dict, include_devel: bool = True) -> list: + """Gets the list of sources from a toml parsed lockfile. + + Args: + parsed_lockfile (dict): The dictionary of the parsed lockfile. + include_devel (bool): Include dev dependencies, defaults to True. + + Returns (list): The sources. + + """ + sources = [] + hash_re = re.compile(r"(sha1|sha224|sha384|sha256|sha512|md5):([a-f0-9]+)") + for section, packages in parsed_lockfile.items(): + if section == "package": + for package in packages: + if "category" not in package or ( + ( + package.get("category") == "dev" + and include_devel + and not package.get("optional") + ) + or ( + package.get("category") == "main" + and not package.get("optional") + ) + ): + hashes = [] + # Check for old metadata format (poetry version < 1.0.0b2) + if "hashes" in parsed_lockfile["metadata"]: + hashes = parsed_lockfile["metadata"]["hashes"][package["name"]] + # metadata format 1.1 + elif "files" in parsed_lockfile["metadata"]: + for package_name in parsed_lockfile["metadata"]["files"]: + if package_name == package["name"]: + package_files = parsed_lockfile["metadata"]["files"][ + package["name"] + ] + num_files = len(package_files) + for num in range(num_files): + match = hash_re.search(package_files[num]["hash"]) + if match: + hashes.append(match.group(2)) + # metadata format 2.0 + else: + for file in package["files"]: + match = hash_re.search(file["hash"]) + if match: + hashes.append(match.group(2)) + url, hash = get_pypi_source( + package["name"], package["version"], hashes + ) + source = {"type": "file", "url": url, "sha256": hash} + sources.append(source) + return sources + + +def get_dep_names(parsed_lockfile: dict, include_devel: bool = True) -> list: + """Gets the list of dependency names. + + Args: + parsed_lockfile (dict): The dictionary of the parsed lockfile. + include_devel (bool): Include dev dependencies, defaults to True. + + Returns (list): The dependency names. + + """ + dep_names = [] + for section, packages in parsed_lockfile.items(): + if section == "package": + for package in packages: + if "category" not in package or ( + ( + package.get("category") == "dev" + and include_devel + and not package.get("optional") + ) + or ( + package.get("category") == "main" + and not package.get("optional") + ) + ): + dep_names.append(package["name"]) + return dep_names + + +def main(): + parser = argparse.ArgumentParser(description="Flatpak Poetry generator") + parser.add_argument("lockfile", type=str) + parser.add_argument( + "-o", type=str, dest="outfile", default="generated-poetry-sources.json" + ) + parser.add_argument("--production", action="store_true", default=False) + args = parser.parse_args() + + include_devel = not args.production + outfile = args.outfile + lockfile = args.lockfile + + print('Scanning "%s" ' % lockfile, file=sys.stderr) + + with open(lockfile, "r") as f: + parsed_lockfile = toml.load(f) + dep_names = get_dep_names(parsed_lockfile, include_devel=include_devel) + pip_command = [ + "pip3", + "install", + "--no-index", + '--find-links="file://${PWD}"', + "--prefix=${FLATPAK_DEST}", + " ".join(dep_names), + ] + main_module = OrderedDict( + [ + ("name", "poetry-deps"), + ("buildsystem", "simple"), + ("build-commands", [" ".join(pip_command)]), + ] + ) + sources = get_module_sources(parsed_lockfile, include_devel=include_devel) + main_module["sources"] = sources + + print(" ... %d new entries" % len(sources), file=sys.stderr) + + print('Writing to "%s"' % outfile) + with open(outfile, "w") as f: + f.write(json.dumps(main_module, indent=4)) + + +if __name__ == "__main__": + main() diff --git a/generated-poetry-sources.json b/generated-poetry-sources.json new file mode 100644 index 00000000..f15b254e --- /dev/null +++ b/generated-poetry-sources.json @@ -0,0 +1,154 @@ +{ + "name": "poetry-deps", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} certifi charset-normalizer colorama cython exceptiongroup flake8 idna iniconfig mccabe packaging pluggy psutil pycodestyle pyflakes pyside6-addons pyside6-essentials pytest python-dateutil pyyaml requests setuptools shiboken6 six suntime systemd-python toml tomli urllib3 wheel" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl", + "sha256": "dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", + "sha256": "3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/7e/26/9d8de10005fedb1eceabe713348d43bae1dbab1786042ca0751a2e2b0f8c/Cython-0.29.37-py2.py3-none-any.whl", + "sha256": "95f1d6a83ef2729e67b3fa7318c829ce5b07ac64c084cd6af11c228e0364662c" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/01/90/79fe92dd413a9cab314ef5c591b5aa9b9ba787ae4cadab75055b0ae00b33/exceptiongroup-1.2.1-py3-none-any.whl", + "sha256": "5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/e3/01/cc8cdec7b61db0315c2ab62d80677a138ef06832ec17f04d87e6ef858f7f/flake8-7.0.0-py2.py3-none-any.whl", + "sha256": "a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl", + "sha256": "82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", + "sha256": "b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", + "sha256": "6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", + "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/a5/5b/0cc789b59e8cc1bf288b38111d002d8c5917123194d45b29dcdac64723cc/pluggy-1.4.0-py3-none-any.whl", + "sha256": "7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/90/c7/6dc0a455d111f68ee43f27793971cf03fe29b6ef972042549db29eec39a2/psutil-5.9.8.tar.gz", + "sha256": "6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/b1/90/a998c550d0ddd07e38605bb5c455d00fcc177a800ff9cc3dafdcb3dd7b56/pycodestyle-2.11.1-py2.py3-none-any.whl", + "sha256": "44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", + "sha256": "84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/02/fc/e265aa0c338ddd8a4f2c3526aadc58f60980508ac56999ba79cf2ce744a7/PySide6_Addons-6.6.3.1-cp38-abi3-manylinux_2_28_x86_64.whl", + "sha256": "7373479565e5bd963b9662857c40c20768bc0b5853334e2076a62cb039e91f74" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/4a/29/2375cccf188862c3297f40cb06832cd48fd98fd5da73b0b296a59f54c9f4/PySide6_Essentials-6.6.3.1-cp38-abi3-manylinux_2_28_x86_64.whl", + "sha256": "1f41f357ce2384576581e76c9c3df1c4fa5b38e347f0bcd0cae7c5bce42a917c" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/4d/7e/c79cecfdb6aa85c6c2e3cf63afc56d0f165f24f5c66c03c695c4d9b84756/pytest-8.1.1-py3-none-any.whl", + "sha256": "2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", + "sha256": "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/cd/e5/af35f7ea75cf72f2cd079c95ee16797de7cd71f29ea7c68ae5ce7be1eda0/PyYAML-6.0.1.tar.gz", + "sha256": "bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", + "sha256": "58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/f7/29/13965af254e3373bceae8fb9a0e6ea0d0e571171b80d6646932131d6439b/setuptools-69.5.1-py3-none-any.whl", + "sha256": "c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/77/f1/feb2a8be699f91fb27fbe8758b405fb38a22e3ae5bd5e05258dbef18d462/shiboken6-6.6.3.1-cp38-abi3-manylinux_2_28_x86_64.whl", + "sha256": "b1aeff0d79d84ddbdc9970144c1bbc3a52fcb45618d1b33d17d57f99f1246d45" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", + "sha256": "8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/d1/d5/bb9997169b8b64d48f9a807fb2ec2413ff5e75c4b77612e75dd0aac8369c/suntime-1.3.2-py3-none-any.whl", + "sha256": "33ac6ec2a3e14758cc690f7573f689d19c3131a6c9753f1bb54460bd70372ca4" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/10/9e/ab4458e00367223bda2dd7ccf0849a72235ee3e29b36dce732685d9b7ad9/systemd-python-235.tar.gz", + "sha256": "4e57f39797fd5d9e2d22b8806a252d7c0106c936039d1e71c8c6b8008e695c0a" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", + "sha256": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", + "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl", + "sha256": "450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", + "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81" + } + ] +} \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 2b4e2898..f8331fc0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "certifi" @@ -121,15 +121,66 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "cython" +version = "0.29.37" +description = "The Cython compiler for writing C extensions for the Python language." +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "Cython-0.29.37-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2d621fe4cb50007446742134a890500b34e3f50abaf7993baaca02634af7e15"}, + {file = "Cython-0.29.37-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d94caf90ae9cb56116ca6d54cdcbccd3c4df6b0cb7233922b2233ee7fe81d05b"}, + {file = "Cython-0.29.37-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:852cd4378cbc9ade02f53709107ff9fdad55019a3a636e8a27663ba6cfce10b6"}, + {file = "Cython-0.29.37-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bbce388431a2608a81c8ab13cb14c50611473843ca766031b8b24bb1723faf79"}, + {file = "Cython-0.29.37-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4658499a41255431f6bbdca7e634e9c8d3a4c190bf24b4aa1646dac751d3da4d"}, + {file = "Cython-0.29.37-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:12192ab269e7185720f2d2f8894587bf1da4276db1b9b869e4622a093f18cae6"}, + {file = "Cython-0.29.37-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:9450e0766ab65947f8a2a36f9e59079fc879c3807ec936c61725a48c97741a52"}, + {file = "Cython-0.29.37-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:177481b0a7e003e5c49e2bf0dda1d6fe610c239f17642a5da9f18c2ad0c5f6b6"}, + {file = "Cython-0.29.37-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b048354fd380278f2fa096e7526973beb6e0491a9d44d7e4e29df52612d25776"}, + {file = "Cython-0.29.37-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ea6d208be1906c5df25b674777d5905c6d8e9ef0b201b830849e0729ba08caba"}, + {file = "Cython-0.29.37-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:af03854571738307a5f30cc6b724081d72db12f907699e7fdfc04c12c839158e"}, + {file = "Cython-0.29.37-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c33508ede9172a6f6f99d5a6dadc7fee23c840423b411ef8b5a403c04e530297"}, + {file = "Cython-0.29.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8af5975ecfae254d8c0051204fca995dda8f93cf9f0bbf7571e3cda2b0cef4d"}, + {file = "Cython-0.29.37-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29415d8eb2fdc1ea518ca4810c50a2d062b387d4c9fbcfb3352346e93db22c6d"}, + {file = "Cython-0.29.37-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe0eaf6b1e9ee97c5ee7bfc943f00e36cf59d929db16886cb018352bff8208da"}, + {file = "Cython-0.29.37-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cc1b9ce2b73b9ee8c305e06173b35c7c202d4b82d084a0cd73dcedfd6d310aec"}, + {file = "Cython-0.29.37-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2618af0b8df26d32ee4e8858d4ad8167546596762620aeade84954ae37194a0e"}, + {file = "Cython-0.29.37-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ac910a28a2fd3d280faf3077b6fe63b97a4b93994ff05647581846f0e4b2f8d1"}, + {file = "Cython-0.29.37-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:8bf38373773f967cfd793997a6fb96cf972d41a9fce987ace5767349d6f15572"}, + {file = "Cython-0.29.37-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6cddb567dadb3aa3e280a8a35e5126030915ea744c2812206e9c194b8881475d"}, + {file = "Cython-0.29.37-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:79ecfc48694e156402c05561e0adb0e25a6e9d35ac0b41693733a08219d38c58"}, + {file = "Cython-0.29.37-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:9a455347e20ddfad0c5dfee32a3e855ee96811269e5fd86be622ddc4cb326404"}, + {file = "Cython-0.29.37-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:fa5b6a0f69bf1823c9fd038fa77a2568b78fda2de045a95b48a71dee4d0d578f"}, + {file = "Cython-0.29.37-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a6164a05440dcd9daa760c6488bc91bdac1380c7b4b3aca38cf307ba66042d54"}, + {file = "Cython-0.29.37-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:562f8f911dbd6f1a1b9be8f6cba097125700355688f613994ccd4406f220557a"}, + {file = "Cython-0.29.37-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c39c2f5a0fe29bb01de9b1fb449bf65bed6f192317c677f181732791c63fe28"}, + {file = "Cython-0.29.37-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0a0a6d5972bb3b8c7363cf19a42a988bb0c0bb5ebd9c736c84eca85113ccfdbe"}, + {file = "Cython-0.29.37-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b82584836e9e7c0d6effee976595e5cd7fa88dbef3e96e900187983c1d4637d1"}, + {file = "Cython-0.29.37-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b6c48f1032b379135a5b4a31976d6c468e02490688acf9254c6c8ed27bd4cbd4"}, + {file = "Cython-0.29.37-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3f87bef1808d255cf13be378c7ad27ae7c6db6df7732217d32428d1daf4109be"}, + {file = "Cython-0.29.37-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:9e68bafeeb97d5a403fb1f7700bd4a55a1f8989824c323ae02ae8a4fcd88f6a1"}, + {file = "Cython-0.29.37-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e14cd44c830e53cf9d7269c87a6bcc638bb065ec07e24990e338162c7001d3c3"}, + {file = "Cython-0.29.37-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0544f7a3e4437b89b356baa15387494c18214e03f2ffaddada5a2c71c3dfd24b"}, + {file = "Cython-0.29.37-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2de3e729d25f041036e81e2f15683dd129f977dfb5b06267e30e8d7acec43225"}, + {file = "Cython-0.29.37-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2ad634dc77a6a74022881826099eccac19c9b79153942cc82e754ffac2bec116"}, + {file = "Cython-0.29.37-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e841a8b4f9ceefb2916e32dac4f28a895cd519e8ece71505144da1ee355c548a"}, + {file = "Cython-0.29.37-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:6c672089fba6a8f6690b8d7924a58c04477771401ad101d53171a13405ee12cb"}, + {file = "Cython-0.29.37-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0301d4739c6894e012f1d410052082fdda9e63888c815d9e23e0f7f82fff7d79"}, + {file = "Cython-0.29.37-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af8e7b4397620e2d18259a11f3bfa026eff9846657e397d02616962dd5dd035a"}, + {file = "Cython-0.29.37-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b225d5e2091c224d4ab328165fef224ba3919b3ed44bd9b3241416f523b4d51a"}, + {file = "Cython-0.29.37-py2.py3-none-any.whl", hash = "sha256:95f1d6a83ef2729e67b3fa7318c829ce5b07ac64c084cd6af11c228e0364662c"}, + {file = "Cython-0.29.37.tar.gz", hash = "sha256:f813d4a6dd94adee5d4ff266191d1d95bf6d4164a4facc535422c021b2504cfb"}, +] + [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -329,6 +380,66 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + [[package]] name = "requests" version = "2.31.0" @@ -350,6 +461,22 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "setuptools" +version = "69.5.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "shiboken6" version = "6.6.3.1" @@ -398,6 +525,17 @@ files = [ {file = "systemd-python-235.tar.gz", hash = "sha256:4e57f39797fd5d9e2d22b8806a252d7c0106c936039d1e71c8c6b8008e695c0a"}, ] +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + [[package]] name = "tomli" version = "2.0.1" @@ -426,7 +564,21 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "wheel" +version = "0.43.0" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "wheel-0.43.0-py3-none-any.whl", hash = "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81"}, + {file = "wheel-0.43.0.tar.gz", hash = "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85"}, +] + +[package.extras] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] + [metadata] lock-version = "2.0" python-versions = "^3.10,<3.13" -content-hash = "52a91159e1368693df4515db0a500ea77d42483e18c7b6f57ea0d8677f521700" +content-hash = "0fafd0ba61f1f9f40d4cf556a63e2ed5e073dc6c36a7bc56183be2d7bcd56725" diff --git a/pyproject.toml b/pyproject.toml index 3c0fec87..27241891 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,9 @@ repository = "https://github.com/oskarsh/Yin-Yang" license = "MIT" readme = "README.md" -packages = [{include = "yin_yang"}] +packages = [ + { include = "yin_yang" } +] [tool.poetry.dependencies] python = "^3.10,<3.13" @@ -23,7 +25,27 @@ requests = "2.31.0" [tool.poetry.group.DEV.dependencies] flake8 = "^7.0.0" pytest = "^8.1.1" +pyyaml = "^6.0.1" +toml = "^0.10.2" +setuptools = "^69.5.1" +wheel = "^0.43.0" +cython = "<3.0" [build-system] requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" \ No newline at end of file +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +yin-yang = "yin_yang:__main__" + +[tool.pyright] +include = ["yin_yang"] +exclude = ["**/node_modules", + "**/__pycache__", + "build", + "pytest_cache", + ".flatpak-builder" +] + +[tool.pytest.ini_options] +addopts = "--import-mode=importlib" \ No newline at end of file diff --git a/scripts/runner.sh b/scripts/runner.sh new file mode 100644 index 00000000..992fbf60 --- /dev/null +++ b/scripts/runner.sh @@ -0,0 +1,2 @@ +#!/bin/sh +python3 -m yin_yang $@ diff --git a/sh.oskar.yin-yang.json b/sh.oskar.yin-yang.json new file mode 100644 index 00000000..1bf0de24 --- /dev/null +++ b/sh.oskar.yin-yang.json @@ -0,0 +1,56 @@ +{ + "id": "sh.oskar.yin-yang", + "runtime": "org.kde.Platform", + "runtime-version": "5.15-23.08", + "sdk": "org.kde.Sdk", + "command": "runner.sh", + "finish-args": [ + "--share=network", + "--socket=x11", + "--socket=wayland", + "--socket=system-bus", + "--socket=session-bus", + "--share=ipc", + "--device=dri", + "--filesystem=host:rw", + "--filesystem=~/.mozilla:rw" + ], + "modules": [ + "generated-poetry-sources.json", + { + "name": "yin-yang", + "buildsystem": "simple", + "build-commands": [ + "find dist -name 'yin_yang-*-py3-none-any.whl' -exec pip install --no-deps --no-build-isolation --prefix=/app {} \\;", + "install -D scripts/runner.sh /app/bin/runner.sh" + ], + "sources": [ + { + "type": "dir", + "path": "." + } + ] + }, + { + "name": "yin-yang-metadata", + "buildsystem": "simple", + "build-commands": [ + "install -Dm644 Yin-Yang.desktop /app/share/applications/sh.oskar.yin-yang.desktop", + "sed -i 's|Path=/opt/yin-yang||' /app/share/applications/sh.oskar.yin-yang.desktop", + "sed -i 's|Icon=yin_yang|Icon=sh.oskar.yin-yang|' /app/share/applications/sh.oskar.yin-yang.desktop", + "sed -i 's|Exec=yin-yang|Exec=runner.sh|' /app/share/applications/sh.oskar.yin-yang.desktop", + "install -Dm664 logo.svg /app/share/icons/hicolor/256x256/apps/sh.oskar.yin-yang.svg" + ], + "sources": [ + { + "type": "file", + "path": "./resources/Yin-Yang.desktop" + }, + { + "type": "file", + "path": "./resources/logo.svg" + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/test_daemon_handler.py b/tests/test_daemon_handler.py index 0d845b43..c8a32096 100644 --- a/tests/test_daemon_handler.py +++ b/tests/test_daemon_handler.py @@ -1,11 +1,12 @@ -import pathlib +from pathlib import Path +import re import shutil import subprocess import unittest from datetime import time from os.path import isfile -from yin_yang import daemon_handler +from yin_yang import daemon_handler, helpers from yin_yang.config import config from yin_yang.meta import Modes, ConfigEvent @@ -26,9 +27,16 @@ def tearDown(self) -> None: def setUpClass(cls) -> None: super().setUpClass() if not isfile(daemon_handler.TIMER_PATH): - pathlib.Path(daemon_handler.SYSTEMD_PATH).mkdir(parents=True, exist_ok=True) + Path(daemon_handler.SYSTEMD_PATH).mkdir(parents=True, exist_ok=True) shutil.copyfile('./resources/yin_yang.timer', daemon_handler.TIMER_PATH) shutil.copyfile('./resources/yin_yang.service', daemon_handler.SERVICE_PATH) + # If we're in a flatpak, the service file needs to be updated + if (helpers.is_flatpak()): + with open(daemon_handler.SERVICE_PATH, 'r') as service: + lines = service.readlines() + with open(daemon_handler.SERVICE_PATH, 'w') as service: + for line in lines: + service.write(re.sub('ExecStart=\/usr\/bin\/yin-yang --systemd', 'ExecStart='+str(Path.home())+'\/.local\/share\/flatpak\/exports\/bin\/sh.oskar.yin-yang --systemd', line)) shutil.copyfile(daemon_handler.TIMER_PATH, daemon_handler.TIMER_PATH.with_suffix('.timer_backup')) @classmethod diff --git a/yin_yang/NotificationHandler.py b/yin_yang/NotificationHandler.py index e001d768..cbd828e8 100644 --- a/yin_yang/NotificationHandler.py +++ b/yin_yang/NotificationHandler.py @@ -1,6 +1,6 @@ from logging import Handler -from PySide6.QtDBus import QDBusMessage, QDBusConnection +from PySide6.QtDBus import QDBusConnection, QDBusMessage def create_dbus_message(title: str, body: str): @@ -8,7 +8,7 @@ def create_dbus_message(title: str, body: str): 'org.freedesktop.portal.Desktop', '/org/freedesktop/portal/desktop', 'org.freedesktop.portal.Notification', - 'AddNotification' + 'AddNotification', ) notification = { @@ -18,16 +18,14 @@ def create_dbus_message(title: str, body: str): 'priority': 'low', } - message.setArguments([ - 'YingYang.ThemeChanged', - notification - ]) + message.setArguments(['YingYang.ThemeChanged', notification]) return message class NotificationHandler(Handler): """Shows logs as notifications""" + def emit(self, record): connection = QDBusConnection.sessionBus() message = create_dbus_message(record.levelname, str(record.msg)) diff --git a/yin_yang/daemon_handler.py b/yin_yang/daemon_handler.py index 6b845bf8..1342720d 100644 --- a/yin_yang/daemon_handler.py +++ b/yin_yang/daemon_handler.py @@ -1,9 +1,11 @@ import logging +import re import shutil -import subprocess from enum import Enum, auto from pathlib import Path +from yin_yang import helpers + from .config import ConfigWatcher, config from .meta import ConfigEvent, Modes @@ -21,10 +23,24 @@ def create_files(): shutil.copy('./resources/yin_yang.timer', TIMER_PATH) if not SERVICE_PATH.is_file(): shutil.copy('./resources/yin_yang.service', SERVICE_PATH) + if helpers.is_flatpak(): + with open(SERVICE_PATH, 'r') as service: + lines = service.readlines() + with open(SERVICE_PATH, 'w') as service: + for line in lines: + service.write( + re.sub( + 'ExecStart=\/usr\/bin\/yin-yang --systemd', + 'ExecStart=' + + str(Path.home()) + + '/.local/share/flatpak/exports/bin/sh.oskar.yin-yang --systemd', + line, + ) + ) def run_command(command, **kwargs): - return subprocess.run(['systemctl', '--user', command, 'yin_yang.timer'], **kwargs) + return helpers.run(['systemctl', '--user', command, 'yin_yang.timer'], **kwargs) def update_times(): @@ -48,7 +64,7 @@ def update_times(): with TIMER_PATH.open('w') as file: file.writelines(lines) - subprocess.run(['systemctl', '--user', 'daemon-reload']) + helpers.run(['systemctl', '--user', 'daemon-reload']) run_command('start') @@ -60,7 +76,9 @@ class _UpdateTimerStatus(Enum): STOP = auto() def __init__(self): - self._next_timer_update: SaveWatcher._UpdateTimerStatus = SaveWatcher._UpdateTimerStatus.NO_UPDATE + self._next_timer_update: SaveWatcher._UpdateTimerStatus = ( + SaveWatcher._UpdateTimerStatus.NO_UPDATE + ) def _set_needed_updates(self, change_values): assert change_values['old_value'] != change_values['new_value'], 'No change!' @@ -73,7 +91,9 @@ def _set_needed_updates(self, change_values): elif change_values['new_value'] == Modes.MANUAL.value: self._next_timer_update = SaveWatcher._UpdateTimerStatus.STOP else: - self._next_timer_update = SaveWatcher._UpdateTimerStatus.UPDATE_TIMES + self._next_timer_update = ( + SaveWatcher._UpdateTimerStatus.UPDATE_TIMES + ) case 'times' | 'coordinates' | 'boot_offset': self._next_timer_update = SaveWatcher._UpdateTimerStatus.UPDATE_TIMES @@ -81,7 +101,10 @@ def _update_timer(self): match self._next_timer_update: case SaveWatcher._UpdateTimerStatus.STOP: run_command('stop') - case SaveWatcher._UpdateTimerStatus.UPDATE_TIMES | SaveWatcher._UpdateTimerStatus.START: + case ( + SaveWatcher._UpdateTimerStatus.UPDATE_TIMES + | SaveWatcher._UpdateTimerStatus.START + ): update_times() self._next_timer_update = SaveWatcher._UpdateTimerStatus.NO_UPDATE diff --git a/yin_yang/helpers.py b/yin_yang/helpers.py new file mode 100644 index 00000000..79d8c3d6 --- /dev/null +++ b/yin_yang/helpers.py @@ -0,0 +1,78 @@ +import os +import subprocess + +"""Check output of a command. + +This is a helper method which will change how we check output depending on if +The application is running in a Flatpak or not. +""" + +"""Base Flatpak Arguments + +These are the base arguments we use to execute commands when running in +a flatpak environment. +""" +base_flatpak_args = ['flatpak-spawn', '--host'] + + +def check_output(args, universal_newlines=False) -> bytes: + try: + output = subprocess.check_output( + args=args, universal_newlines=universal_newlines + ) + return output + except FileNotFoundError: + flatpak_args = base_flatpak_args + args + return subprocess.check_output( + args=flatpak_args, universal_newlines=universal_newlines + ) + + +def check_call(command, stdout=None) -> int: + try: + return subprocess.check_call(command, stdout=stdout) + except FileNotFoundError: + flatpak_args = base_flatpak_args + command + return subprocess.check_call(flatpak_args, stdout=stdout) + + +def is_flatpak() -> bool: + return os.path.isfile('/.flatpak-info') + + +def get_usr() -> str: + """ + Returns the proper path to /usr. + This is need as the path to /usr is different in a flatpak environment. + :return: The path to /usr with a trailing / + """ + if is_flatpak(): + return '/var/run/host/usr/' + return '/usr/' + + +def get_etc() -> str: + """ + Returns the proper path to /etc. + This is need as the path to /etc is different in a flatpak environment. + :return: The path to /etc with a trailing / + """ + if is_flatpak(): + return '/var/run/host/etc/' + return '/etc/' + + +def run( + command: list[str], kwargs: list[str] = [], stdout=None +) -> subprocess.CompletedProcess[str]: + try: + if len(kwargs) == 0: + return subprocess.run(command, stdout=stdout) + else: + return subprocess.run(command, **kwargs, stdout=stdout) + except FileNotFoundError: + flatpak_args = base_flatpak_args + command + if len(kwargs) == 0: + return subprocess.run(flatpak_args, stdout=stdout) + else: + return subprocess.run(flatpak_args, **kwargs, stdout=stdout) diff --git a/yin_yang/plugins/_plugin.py b/yin_yang/plugins/_plugin.py index de7abd7d..384156a7 100644 --- a/yin_yang/plugins/_plugin.py +++ b/yin_yang/plugins/_plugin.py @@ -10,6 +10,7 @@ from PySide6.QtGui import QColor, QRgba64 from PySide6.QtWidgets import QGroupBox, QHBoxLayout, QLineEdit, QComboBox +from yin_yang import helpers from ..meta import UnsupportedDesktopError, FileFormat logger = logging.getLogger(__name__) @@ -138,7 +139,7 @@ def set_theme(self, theme: str): # insert theme in command and run it command = self.insert_theme(theme) - subprocess.check_call(command) + helpers.check_call(command) def insert_theme(self, theme: str) -> list: command = self.command.copy() @@ -152,7 +153,7 @@ def insert_theme(self, theme: str) -> list: def check_command(command) -> bool: # Returns true if command execution succeeds try: - subprocess.check_call(command, stdout=subprocess.DEVNULL) + helpers.check_call(command, stdout=subprocess.DEVNULL) return True except FileNotFoundError: # if no such command is available, the plugin is not available diff --git a/yin_yang/plugins/colors.py b/yin_yang/plugins/colors.py index 5796d57d..15e7febc 100644 --- a/yin_yang/plugins/colors.py +++ b/yin_yang/plugins/colors.py @@ -1,8 +1,9 @@ -import subprocess import re +from yin_yang import helpers + from ..meta import Desktop -from ._plugin import Plugin, PluginDesktopDependent, PluginCommandline +from ._plugin import Plugin, PluginCommandline, PluginDesktopDependent class Colors(PluginDesktopDependent): @@ -18,7 +19,7 @@ def strategy(self) -> Plugin: class _KDEColors(PluginCommandline): - name = "Colors" + name = 'Colors' translations = {} def __init__(self): @@ -30,8 +31,11 @@ def available_themes(self) -> dict: if self.translations: return self.translations - colors = subprocess.check_output(['plasma-apply-colorscheme', '--list-schemes'], - universal_newlines=True) + colors = str( + helpers.check_output( + ['plasma-apply-colorscheme', '--list-schemes'], universal_newlines=True + ) + ) colors = colors.splitlines() del colors[0] diff --git a/yin_yang/plugins/custom.py b/yin_yang/plugins/custom.py index 2f5547ce..14a0a314 100644 --- a/yin_yang/plugins/custom.py +++ b/yin_yang/plugins/custom.py @@ -1,7 +1,7 @@ -import subprocess - from PySide6.QtWidgets import QLineEdit +from yin_yang import helpers + from ._plugin import PluginCommandline @@ -24,7 +24,7 @@ def get_input(self, widget): def set_theme(self, theme: str): if not theme: - raise ValueError(f'Theme \"{theme}\" is invalid') + raise ValueError(f'Theme "{theme}" is invalid') if not (self.available and self.enabled): return @@ -32,4 +32,4 @@ def set_theme(self, theme: str): # insert theme in command and run it command = self.insert_theme(theme) # set shell=True to avoid having to separate between arguments - subprocess.check_call(command, shell=True) + helpers.check_call(command, shell=True) diff --git a/yin_yang/plugins/firefox.py b/yin_yang/plugins/firefox.py index 8407e0c4..a95d347b 100755 --- a/yin_yang/plugins/firefox.py +++ b/yin_yang/plugins/firefox.py @@ -1,6 +1,6 @@ import json -from configparser import ConfigParser import logging +from configparser import ConfigParser from os.path import isdir from pathlib import Path diff --git a/yin_yang/plugins/gtk.py b/yin_yang/plugins/gtk.py index 89ba084f..6a08cd71 100755 --- a/yin_yang/plugins/gtk.py +++ b/yin_yang/plugins/gtk.py @@ -1,13 +1,19 @@ import logging -import subprocess -from os import scandir, path +from os import path, scandir from pathlib import Path from PySide6.QtDBus import QDBusMessage -from ._plugin import PluginDesktopDependent, PluginCommandline, DBusPlugin, themes_from_theme_directories -from .system import test_gnome_availability +from yin_yang import helpers + from ..meta import Desktop +from ._plugin import ( + DBusPlugin, + PluginCommandline, + PluginDesktopDependent, + themes_from_theme_directories, +) +from .system import test_gnome_availability logger = logging.getLogger(__name__) @@ -22,8 +28,10 @@ def __init__(self, desktop: Desktop): case Desktop.GNOME: super().__init__(_Gnome()) if not self.strategy.available: - print('You need to install an extension for gnome to use it. \n' - 'You can get it from here: https://extensions.gnome.org/extension/19/user-themes/') + print( + 'You need to install an extension for gnome to use it. \n' + 'You can get it from here: https://extensions.gnome.org/extension/19/user-themes/' + ) case Desktop.MATE: super().__init__(_Mate()) case Desktop.XFCE: @@ -45,20 +53,24 @@ class _Gnome(PluginCommandline): name = 'GTK' def __init__(self): - super().__init__(['gsettings', 'set', 'org.gnome.desktop.interface', 'gtk-theme', '{theme}']) + super().__init__( + ['gsettings', 'set', 'org.gnome.desktop.interface', 'gtk-theme', '{theme}'] + ) self.theme_light = 'Default' self.theme_dark = 'Default' @property def available(self) -> bool: return test_gnome_availability(self.command) - - + + class _Budgie(PluginCommandline): name = 'GTK' def __init__(self): - super().__init__(['gsettings', 'set', 'org.gnome.desktop.interface', 'gtk-theme', '{theme}']) + super().__init__( + ['gsettings', 'set', 'org.gnome.desktop.interface', 'gtk-theme', '{theme}'] + ) self.theme_light = 'Default' self.theme_dark = 'Default' @@ -77,10 +89,7 @@ def __init__(self): def create_message(self, theme: str) -> QDBusMessage: message = QDBusMessage.createMethodCall( - 'org.kde.GtkConfig', - '/GtkConfig', - 'org.kde.GtkConfig', - 'setGtkTheme' + 'org.kde.GtkConfig', '/GtkConfig', 'org.kde.GtkConfig', 'setGtkTheme' ) message.setArguments([theme]) return message @@ -108,19 +117,23 @@ def set_theme(self, theme: str): f.writelines(lines) # send signal to read new config - subprocess.run(['killall', '-HUP', 'xsettingsd']) + helpers.run(['killall', '-HUP', 'xsettingsd']) class _Xfce(PluginCommandline): def __init__(self): - super(_Xfce, self).__init__(['xfconf-query', '-c', 'xsettings', '-p', '/Net/ThemeName', '-s', '{theme}']) + super(_Xfce, self).__init__( + ['xfconf-query', '-c', 'xsettings', '-p', '/Net/ThemeName', '-s', '{theme}'] + ) self.theme_light = 'Adwaita' self.theme_dark = 'Adwaita-dark' class _Mate(PluginCommandline): def __init__(self): - super().__init__(['dconf', 'write', '/org/mate/desktop/interface/gtk-theme', '\'{theme}\'']) + super().__init__( + ['dconf', 'write', '/org/mate/desktop/interface/gtk-theme', '"{theme}"'] + ) self.theme_light = 'Yaru' self.theme_dark = 'Yaru-dark' @@ -131,7 +144,15 @@ def available(self) -> bool: class _Cinnamon(PluginCommandline): def __init__(self): - super().__init__(['gsettings', 'set', 'org.cinnamon.desktop.interface', 'gtk-theme', '\"{theme}\"']) + super().__init__( + [ + 'gsettings', + 'set', + 'org.cinnamon.desktop.interface', + 'gtk-theme', + '"{theme}"', + ] + ) self.theme_light = 'Adwaita' self.theme_dark = 'Adwaita-dark' diff --git a/yin_yang/plugins/icons.py b/yin_yang/plugins/icons.py index c175ed15..e50c8a9d 100644 --- a/yin_yang/plugins/icons.py +++ b/yin_yang/plugins/icons.py @@ -1,9 +1,10 @@ -from .system import test_gnome_availability -from ..meta import Desktop -from ._plugin import PluginDesktopDependent, PluginCommandline import configparser +from os import path, scandir from pathlib import Path -from os import scandir, path + +from ..meta import Desktop +from ._plugin import PluginCommandline, PluginDesktopDependent +from .system import test_gnome_availability theme_directories = ['/usr/share/icons', f'{Path.home()}/.icons'] @@ -25,7 +26,9 @@ def __init__(self, desktop: Desktop): class _Mate(PluginCommandline): def __init__(self): - super().__init__(['dconf', 'write', '/org/mate/desktop/interface/icon-theme', '\'{theme}\'']) + super().__init__( + ['dconf', 'write', '/org/mate/desktop/interface/icon-theme', '\"{theme}\"'] + ) self.theme_light = 'Yaru' self.theme_dark = 'Yaru-dark' @@ -36,18 +39,34 @@ def available(self): class _Cinnamon(PluginCommandline): def __init__(self): - super().__init__(['gsettings', 'set', 'org.cinnamon.desktop.interface', 'icon-theme', '\"{theme}\"']) + super().__init__( + [ + 'gsettings', + 'set', + 'org.cinnamon.desktop.interface', + 'icon-theme', + '\"{theme}\"', + ] + ) self.theme_light = 'Mint-X' self.theme_dark = 'gnome' @property def available(self) -> bool: return test_gnome_availability(self.command) - + class _Budgie(PluginCommandline): def __init__(self): - super().__init__(['gsettings', 'set', 'org.gnome.desktop.interface', 'icon-theme', '\"{theme}\"']) + super().__init__( + [ + 'gsettings', + 'set', + 'org.gnome.desktop.interface', + 'icon-theme', + '\"{theme}\"', + ] + ) self.theme_light = 'Default' self.theme_dark = 'Default' @@ -64,10 +83,15 @@ def available_themes(self) -> dict: continue with scandir(directory) as entries: - themes.extend(d.name for d in entries if d.is_dir() and path.isfile(d.path + '/index.theme')) + themes.extend( + d.name + for d in entries + if d.is_dir() and path.isfile(d.path + '/index.theme') + ) return {t: t for t in themes} + class _Kde(PluginCommandline): def __init__(self): super().__init__(["/usr/lib/plasma-changeicons", r"{theme}"]) diff --git a/yin_yang/plugins/konsole.py b/yin_yang/plugins/konsole.py index 194f3c6d..cf304e08 100644 --- a/yin_yang/plugins/konsole.py +++ b/yin_yang/plugins/konsole.py @@ -10,18 +10,21 @@ import psutil from PySide6.QtDBus import QDBusConnection, QDBusMessage +from yin_yang import helpers + from ._plugin import Plugin logger = logging.getLogger(__name__) class Konsole(Plugin): - """ + ''' Themes are profiles. To use a color scheme, create a new profile or edit one to use the desired color scheme. This is necessary to allow live theme changes. - """ - global_path = Path('/usr/share/konsole') + ''' + + global_path = Path(helpers.get_usr() + 'share/konsole') config_path = Path.home() / '.config/konsolerc' @property @@ -65,7 +68,8 @@ def set_mode(self, dark: bool) -> bool: # Get the process IDs of all running Konsole instances owned by the current user process_ids = [ - proc.pid for proc in psutil.process_iter() + proc.pid + for proc in psutil.process_iter() if proc.name() == 'konsole' and proc.username() == os.getlogin() ] @@ -82,7 +86,8 @@ def set_mode(self, dark: bool) -> bool: set_profile('org.kde.yakuake', profile, set_default_profile=True) process_ids = [ - proc.pid for proc in psutil.process_iter() + proc.pid + for proc in psutil.process_iter() if proc.name() == 'dolphin' and proc.username() == os.getlogin() ] @@ -90,8 +95,7 @@ def set_mode(self, dark: bool) -> bool: for proc_id in process_ids: logger.debug(f'Changing profile in dolphin session {proc_id}') set_profile(f'org.kde.dolphin-{proc_id}', profile) - set_profile(f'org.kde.dolphin-{proc_id}', - profile, set_default_profile=True) + set_profile(f'org.kde.dolphin-{proc_id}', profile, set_default_profile=True) return True @@ -104,11 +108,15 @@ def available_themes(self) -> dict: if not self.available: return {} - themes = dict(sorted([ - (p.with_suffix('').name, p) - for p in chain(self.global_path.iterdir(), self.user_path.iterdir()) - if p.is_file() and p.suffix == '.colorscheme' - ])) + themes = dict( + sorted( + [ + (p.with_suffix('').name, p) + for p in chain(self.global_path.iterdir(), self.user_path.iterdir()) + if p.is_file() and p.suffix == '.colorscheme' + ] + ) + ) themes_dict = {} config_parser = ConfigParser() @@ -131,7 +139,7 @@ def default_profile(self): # cant use config parser because of weird file structure with self.config_path.open('r') as file: for line in file: - # Search for the pattern "DefaultProfile=*" + # Search for the pattern 'DefaultProfile=*' match = re.search(r'DefaultProfile=(.*)', line) # If a match is found, return the content of the wildcard '*' @@ -151,14 +159,14 @@ def default_profile(self): if value is None: # create a custom profile manually - file_content = """[Appearance] + file_content = '''[Appearance] ColorScheme=Breeze [General] Command=/bin/bash Name=Fish Parent=FALLBACK/ -""" +''' with (self.user_path / 'Default.profile').open('w') as file: file.writelines(file_content) @@ -176,7 +184,7 @@ def default_profile(self, value: str): with self.config_path.open('r') as file: lines = file.readlines() for i, line in enumerate(lines): - # Search for the pattern "DefaultProfile=*" + # Search for the pattern 'DefaultProfile=*' match = re.search(r'DefaultProfile=(.*)', line) # If a match is found, return the content of the wildcard '*' @@ -215,7 +223,9 @@ def update_profile(self, dark: bool, theme: str): profile_config.write(file) def create_profiles(self): - logger.debug('Creating new profiles for live-switching between light and dark themes.') + logger.debug( + 'Creating new profiles for live-switching between light and dark themes.' + ) # copy default profile to create theme profiles light_profile = self.user_path / 'Light.profile' dark_profile = self.user_path / 'Dark.profile' @@ -254,32 +264,28 @@ def set_profile(service: str, profile: str, set_default_profile: bool = False): # maybe it's possible with pyside6 dbus packages, but this was simpler and worked try: - sessions = subprocess.check_output( - f'qdbus {service} | grep "{path}"', shell=True) + sessions = helpers.check_output(f'qdbus {service} | grep "{path}"') except subprocess.CalledProcessError: try: - sessions = subprocess.check_output( + sessions = helpers.check_output( f'qdbus org.kde.konsole | grep "{path}"', shell=True ) logger.debug(f'Found org.kde.konsole, use that instead') - service = "org.kde.konsole" + service = 'org.kde.konsole' except subprocess.CalledProcessError: # happens when dolphins konsole is not opened logger.debug( - f'No Konsole sessions available in service {service}, skipping') + f'No Konsole sessions available in service {service}, skipping' + ) return sessions = sessions.decode('utf-8').removesuffix('\n').split('\n') # loop: process sessions for session in sessions: logger.debug( - f'Changing {"default" if set_default_profile else ""} profile of session {session} to {profile}') - # set profile - message = QDBusMessage.createMethodCall( - service, - session, - interface, - method + f'Changing {"default" if set_default_profile else ""} profile of session {session} to {profile}' ) + # set profile + message = QDBusMessage.createMethodCall(service, session, interface, method) message.setArguments([profile]) connection.call(message) diff --git a/yin_yang/plugins/kvantum.py b/yin_yang/plugins/kvantum.py index f9befeb7..4e83292b 100755 --- a/yin_yang/plugins/kvantum.py +++ b/yin_yang/plugins/kvantum.py @@ -1,6 +1,8 @@ from os import walk from pathlib import Path +from yin_yang import helpers + from ._plugin import PluginCommandline @@ -24,7 +26,7 @@ def available_themes(self) -> dict: if not self.available: return {} - paths = [Path('/usr/share/Kvantum'), Path.home() / '.config/Kvantum'] + paths = [Path(helpers.get_usr() + 'share/Kvantum'), Path.home() / '.config/Kvantum'] themes = list() for path in paths: themes = themes + self.get_kvantum_theme_from_dir(path) diff --git a/yin_yang/plugins/system.py b/yin_yang/plugins/system.py index ce24523e..fb94da1a 100644 --- a/yin_yang/plugins/system.py +++ b/yin_yang/plugins/system.py @@ -1,24 +1,28 @@ import json import logging -import subprocess -import pwd import os +import pwd from configparser import ConfigParser from pathlib import Path from PySide6.QtCore import QLocale from PySide6.QtDBus import QDBusMessage, QDBusVariant +from yin_yang import helpers + from ..meta import Desktop -from ._plugin import PluginDesktopDependent, PluginCommandline, themes_from_theme_directories, DBusPlugin +from ._plugin import ( + DBusPlugin, + PluginCommandline, + PluginDesktopDependent, + themes_from_theme_directories, +) logger = logging.getLogger(__name__) def test_gnome_availability(command) -> bool: - return PluginCommandline.check_command( - [command[0], 'get', command[2], command[3]] - ) + return PluginCommandline.check_command([command[0], 'get', command[2], command[3]]) class System(PluginDesktopDependent): @@ -46,7 +50,15 @@ class _Gnome(PluginCommandline): # TODO allow using the default themes, not only user themes def __init__(self): - super().__init__(['gsettings', 'set', 'org.gnome.shell.extensions.user-theme', 'name', '{theme}']) + super().__init__( + [ + 'gsettings', + 'set', + 'org.gnome.shell.extensions.user-theme', + 'name', + '{theme}', + ] + ) @property def available(self) -> bool: @@ -57,7 +69,15 @@ class _Budgie(PluginCommandline): name = 'System' def __init__(self): - super().__init__(['gsettings', 'set', 'com.solus-project.budgie-panel', 'dark-theme', '{theme}']) + super().__init__( + [ + 'gsettings', + 'set', + 'com.solus-project.budgie-panel', + 'dark-theme', + '{theme}', + ] + ) self.theme_light = 'light' self.theme_dark = 'dark' @@ -89,7 +109,7 @@ def available_themes(self) -> dict: def get_readable_kde_theme_name(file) -> str: - """Searches for the long_name in the file and maps it to the found short name""" + '''Searches for the long_name in the file and maps it to the found short name''' for line in file: if 'Name=' in line: @@ -108,9 +128,7 @@ def get_readable_kde_theme_name(file) -> str: def get_name_key(meta): locale = filter( lambda name: name in meta['KPlugin'], - [f'Name[{QLocale().name()}]', - f'Name[{QLocale().language()}]', - 'Name'] + [f'Name[{QLocale().name()}]', f'Name[{QLocale().language()}]', 'Name'], ) return next(locale) @@ -131,11 +149,13 @@ def available_themes(self) -> dict: # aliases for path to use later on user = pwd.getpwuid(os.getuid())[0] - path = "/home/" + user + "/.local/share/plasma/look-and-feel/" + path = '/home/' + user + '/.local/share/plasma/look-and-feel/' # asks the system what themes are available # noinspection SpellCheckingInspection - long_names = subprocess.check_output(["lookandfeeltool", "-l"], universal_newlines=True) + long_names = helpers.check_output( + ['lookandfeeltool', '-l'], universal_newlines=True + ) long_names = long_names.splitlines() long_names.sort() @@ -144,14 +164,20 @@ def available_themes(self) -> dict: # trying to get the Desktop file try: # json in newer versions - with open(f'/usr/share/plasma/look-and-feel/{long_name}/metadata.json', 'r') as file: + with open( + f'{helpers.get_usr()}share/plasma/look-and-feel/{long_name}/metadata.json', + 'r', + ) as file: meta = json.load(file) key = get_name_key(meta) self.translations[long_name] = meta['KPlugin'][key] except OSError: try: # load the name from the metadata.desktop file - with open(f'/usr/share/plasma/look-and-feel/{long_name}/metadata.desktop', 'r') as file: + with open( + f'{helpers.get_usr()}share/plasma/look-and-feel/{long_name}/metadata.desktop', + 'r', + ) as file: self.translations[long_name] = get_readable_kde_theme_name(file) except OSError: # check the next path if the themes exist there @@ -159,7 +185,9 @@ def available_themes(self) -> dict: # load the name from the metadata.desktop file with open(f'{path}{long_name}/metadata.desktop', 'r') as file: # search for the name - self.translations[long_name] = get_readable_kde_theme_name(file) + self.translations[long_name] = get_readable_kde_theme_name( + file + ) except OSError: # if no file exist lets just use the long name self.translations[long_name] = long_name @@ -168,10 +196,15 @@ def available_themes(self) -> dict: class _Mate(PluginCommandline): - theme_directories = [Path('/usr/share/themes'), Path.home() / '.themes'] + theme_directories = [ + Path(helpers.get_usr() + 'share/themes'), + Path.home() / '.themes', + ] def __init__(self): - super().__init__(['dconf', 'write', '/org/mate/marco/general/theme', '\'{theme}\'']) + super().__init__( + ['dconf', 'write', '/org/mate/marco/general/theme', '"{theme}"'] + ) self.theme_light = 'Yaru' self.theme_dark = 'Yaru-dark' @@ -205,7 +238,9 @@ def available(self): class _Cinnamon(PluginCommandline): def __init__(self): - super().__init__(['gsettings', 'set', 'org.cinnamon.theme', 'name', '\"{theme}\"']) + super().__init__( + ['gsettings', 'set', 'org.cinnamon.theme', 'name', '"{theme}"'] + ) self.theme_light = 'Mint-X-Teal' self.theme_dark = 'Mint-Y-Dark-Brown' @@ -217,10 +252,7 @@ def available(self) -> bool: class _Xfce(DBusPlugin): def create_message(self, theme: str) -> QDBusMessage: message = QDBusMessage.createMethodCall( - 'org.xfce.Xfconf', - '/org/xfce/Xfconf', - 'org.xfce.Xfconf', - 'SetProperty' + 'org.xfce.Xfconf', '/org/xfce/Xfconf', 'org.xfce.Xfconf', 'SetProperty' ) theme_variant = QDBusVariant() theme_variant.setVariant(theme) diff --git a/yin_yang/plugins/vscode.py b/yin_yang/plugins/vscode.py index 01b0e566..9418c133 100755 --- a/yin_yang/plugins/vscode.py +++ b/yin_yang/plugins/vscode.py @@ -4,6 +4,7 @@ from os.path import isdir, isfile from pathlib import Path +from .. import helpers from ..meta import FileFormat from ._plugin import flatpak_system, flatpak_user, snap_path, ConfigFilePlugin @@ -13,10 +14,10 @@ str(Path.home() / '.vscode/extensions'), str(Path.home() / '.vscode-insiders/extensions'), str(Path.home() / '.vscode-oss/extensions'), - '/usr/lib/code/extensions', - '/usr/lib/code-insiders/extensions', - '/usr/share/code/resources/app/extensions', - '/usr/share/code-insiders/resources/app/extensions', + helpers.get_usr() + 'lib/code/extensions', + helpers.get_usr() + 'lib/code-insiders/extensions', + helpers.get_usr() + 'share/code/resources/app/extensions', + helpers.get_usr() + 'share/code-insiders/resources/app/extensions', '/usr/share/vscodium/resources/app/extensions', '/usr/share/vscodium-git/resources/app/extensions', '/usr/share/vscodium-insiders/resources/app/extensions', diff --git a/yin_yang/plugins/wallpaper.py b/yin_yang/plugins/wallpaper.py index bc5d0587..eb3885e2 100755 --- a/yin_yang/plugins/wallpaper.py +++ b/yin_yang/plugins/wallpaper.py @@ -4,6 +4,7 @@ from PySide6.QtWidgets import QDialogButtonBox, QVBoxLayout, QWidget, QLineEdit from PySide6.QtDBus import QDBusMessage +from yin_yang import helpers from ..meta import Desktop from ._plugin import PluginDesktopDependent, PluginCommandline, DBusPlugin @@ -139,7 +140,7 @@ def create_message(self, theme: str) -> QDBusMessage: class _Xfce(PluginCommandline): def __init__(self): # first, get all monitors - properties = str(subprocess.check_output(['xfconf-query', '-c', 'xfce4-desktop', '-l'])) + properties = str(helpers.check_output(['xfconf-query', '-c', 'xfce4-desktop', '-l'])) monitor = next(p for p in properties.split('\\n') if p.endswith('/workspace0/last-image')) super().__init__(['xfconf-query', '-c', 'xfce4-desktop', '-p', monitor, '-s', '{theme}'])