From 8518ee3d5efab83a8d1c4b8645aeba00d1c71694 Mon Sep 17 00:00:00 2001 From: "Colin B. Macdonald" Date: Fri, 24 Feb 2023 23:37:38 -0800 Subject: [PATCH 01/19] Consistent json indent --- req2flatpak.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/req2flatpak.py b/req2flatpak.py index e3471a0..972a771 100755 --- a/req2flatpak.py +++ b/req2flatpak.py @@ -639,7 +639,7 @@ def build_module_as_str(cls, *args, **kwargs) -> str: The args and kwargs are the same as in :py:meth:`~req2flatpak.FlatpakGenerator.build_module` """ - return json.dumps(cls.build_module(*args, **kwargs), indent=2) + return json.dumps(cls.build_module(*args, **kwargs), indent=4) # ============================================================================= From 2ea3d70b5a219c15cd5aff5f1de969ee6541f34f Mon Sep 17 00:00:00 2001 From: "Colin B. Macdonald" Date: Fri, 24 Feb 2023 23:39:08 -0800 Subject: [PATCH 02/19] Write yaml if output file ends in yaml or yml extension Start of a fix for Issue #37. --- pyproject.toml | 1 + req2flatpak.py | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 55a2291..8fdb5df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ packaging = { version = "^21.3", optional = true } [tool.poetry.extras] packaging = ["packaging"] +# TODO: something about pyyaml here [tool.poetry.group.lint.dependencies] pylama = { extras = [ diff --git a/req2flatpak.py b/req2flatpak.py index 972a771..88e3f79 100755 --- a/req2flatpak.py +++ b/req2flatpak.py @@ -634,13 +634,26 @@ def sources(downloads: Iterable[Download]) -> List[dict]: @classmethod def build_module_as_str(cls, *args, **kwargs) -> str: """ - Generates a build module for inclusion in a flatpak-builder build manifest. + Generate JSON build module for inclusion in a flatpak-builder build manifest. The args and kwargs are the same as in :py:meth:`~req2flatpak.FlatpakGenerator.build_module` """ return json.dumps(cls.build_module(*args, **kwargs), indent=4) + @classmethod + def build_module_as_yaml_str(cls, *args, **kwargs) -> str: + """ + Generate YAML build module for inclusion in a flatpak-builder build manifest. + + The args and kwargs are the same as in + :py:meth:`~req2flatpak.FlatpakGenerator.build_module` + """ + # optional dependency, not imported at top + import yaml + + return yaml.dump(cls.build_module(*args, **kwargs), indent=2) + # ============================================================================= # CLI commandline interface @@ -682,6 +695,10 @@ def cli_parser() -> argparse.ArgumentParser: nargs="?", type=argparse.FileType("w"), default=sys.stdout, + help=""" + By default, writes JSON but specify a '.yaml' extension and YAML + will be written instead, provided you have the 'pyyaml' package. + """, ) parser.add_argument( "--platform-info", @@ -706,7 +723,13 @@ def main(): options = parser.parse_args() # stream output to a file or to stdout - output_stream = options.outfile if hasattr(options.outfile, "write") else sys.stdout + want_yaml = False + if hasattr(options.outfile, "write"): + output_stream = options.outfile + if pathlib.Path(output_stream.name).suffix.casefold() in (".yaml", ".yml"): + want_yaml = True + else: + output_stream = sys.stdout # print platform info if requested, and exit if options.platform_info: @@ -771,6 +794,17 @@ def main(): # generate flatpak-builder build module build_module = FlatpakGenerator.build_module(requirements, downloads) + if want_yaml: + try: + # optional dependency, not imported at top + import yaml + except ImportError: + parser.error( + "Writing yaml files requires pyyaml package: try 'pip install pyyaml'" + ) + yaml.dump(build_module, output_stream, indent=2) + parser.exit() + # write output json.dump(build_module, output_stream, indent=4) From 1b043293061628b7246ed58cf32808c8b3db3256 Mon Sep 17 00:00:00 2001 From: "Colin B. Macdonald" Date: Sat, 25 Feb 2023 08:51:21 -0800 Subject: [PATCH 03/19] Update pyproject.toml Co-authored-by: yfprojects <62463991+real-yfprojects@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8fdb5df..c63777a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ packaging = { version = "^21.3", optional = true } [tool.poetry.extras] packaging = ["packaging"] -# TODO: something about pyyaml here +yaml = ["pyyaml"] [tool.poetry.group.lint.dependencies] pylama = { extras = [ From 3a2f60d5441f27ffd279d7e31644d91ef5111fee Mon Sep 17 00:00:00 2001 From: "Colin B. Macdonald" Date: Sat, 25 Feb 2023 09:45:32 -0800 Subject: [PATCH 04/19] Add a "--yaml" cmdline arg --- req2flatpak.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/req2flatpak.py b/req2flatpak.py index 88e3f79..42a0333 100755 --- a/req2flatpak.py +++ b/req2flatpak.py @@ -689,6 +689,12 @@ def cli_parser() -> argparse.ArgumentParser: default=False, help="Uses a persistent cache when querying pypi.", ) + parser.add_argument( + "--yaml", + action="store_true", + help="Write YAML instead of the default JSON. Needs the 'pyyaml' package.", + ) + parser.add_argument( "--outfile", "-o", @@ -723,11 +729,10 @@ def main(): options = parser.parse_args() # stream output to a file or to stdout - want_yaml = False if hasattr(options.outfile, "write"): output_stream = options.outfile if pathlib.Path(output_stream.name).suffix.casefold() in (".yaml", ".yml"): - want_yaml = True + options.yaml = True else: output_stream = sys.stdout @@ -794,7 +799,7 @@ def main(): # generate flatpak-builder build module build_module = FlatpakGenerator.build_module(requirements, downloads) - if want_yaml: + if options.yaml: try: # optional dependency, not imported at top import yaml From db2918c5809882307ca5f7e95298adb8ee5f2d94 Mon Sep 17 00:00:00 2001 From: "Colin B. Macdonald" Date: Sat, 25 Feb 2023 09:54:00 -0800 Subject: [PATCH 05/19] write yaml from '--platform-info' too --- req2flatpak.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/req2flatpak.py b/req2flatpak.py index 42a0333..f05941c 100755 --- a/req2flatpak.py +++ b/req2flatpak.py @@ -728,6 +728,16 @@ def main(): parser = cli_parser() options = parser.parse_args() + def _get_yaml_module_or_exit(): + try: + # optional dependency, not imported at top + import yaml + except ImportError: + parser.error( + "Outputing YAML requires 'pyyaml' package: try 'pip install pyyaml'" + ) + return yaml + # stream output to a file or to stdout if hasattr(options.outfile, "write"): output_stream = options.outfile @@ -737,6 +747,12 @@ def main(): output_stream = sys.stdout # print platform info if requested, and exit + if options.platform_info and options.yaml: + yaml = _get_yaml_module_or_exit() + yaml.dump( + asdict(PlatformFactory.from_current_interpreter()), output_stream, indent=2 + ) + parser.exit() if options.platform_info: json.dump( asdict(PlatformFactory.from_current_interpreter()), output_stream, indent=4 @@ -800,13 +816,7 @@ def main(): build_module = FlatpakGenerator.build_module(requirements, downloads) if options.yaml: - try: - # optional dependency, not imported at top - import yaml - except ImportError: - parser.error( - "Writing yaml files requires pyyaml package: try 'pip install pyyaml'" - ) + yaml = _get_yaml_module_or_exit() yaml.dump(build_module, output_stream, indent=2) parser.exit() From 2aa87c472e9bfc6c2013da90b9fcc04a915d58d0 Mon Sep 17 00:00:00 2001 From: "Colin B. Macdonald" Date: Sat, 25 Feb 2023 10:46:06 -0800 Subject: [PATCH 06/19] Refactor yaml --- req2flatpak.py | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/req2flatpak.py b/req2flatpak.py index f05941c..c17962d 100755 --- a/req2flatpak.py +++ b/req2flatpak.py @@ -728,16 +728,6 @@ def main(): parser = cli_parser() options = parser.parse_args() - def _get_yaml_module_or_exit(): - try: - # optional dependency, not imported at top - import yaml - except ImportError: - parser.error( - "Outputing YAML requires 'pyyaml' package: try 'pip install pyyaml'" - ) - return yaml - # stream output to a file or to stdout if hasattr(options.outfile, "write"): output_stream = options.outfile @@ -746,17 +736,22 @@ def _get_yaml_module_or_exit(): else: output_stream = sys.stdout + if options.yaml: + try: + # optional dependency, not imported at top + import yaml + except ImportError: + parser.error( + "Outputing YAML requires 'pyyaml' package: try 'pip install pyyaml'" + ) + # print platform info if requested, and exit - if options.platform_info and options.yaml: - yaml = _get_yaml_module_or_exit() - yaml.dump( - asdict(PlatformFactory.from_current_interpreter()), output_stream, indent=2 - ) - parser.exit() if options.platform_info: - json.dump( - asdict(PlatformFactory.from_current_interpreter()), output_stream, indent=4 - ) + info = asdict(PlatformFactory.from_current_interpreter()) + if options.yaml: + yaml.dump(info, output_stream, indent=2) + else: + json.dump(info, output_stream, indent=4) parser.exit() # print installed packages if requested, and exit @@ -816,7 +811,6 @@ def _get_yaml_module_or_exit(): build_module = FlatpakGenerator.build_module(requirements, downloads) if options.yaml: - yaml = _get_yaml_module_or_exit() yaml.dump(build_module, output_stream, indent=2) parser.exit() From 6f622a82d33a212bc85953b77465504bd6b7de97 Mon Sep 17 00:00:00 2001 From: "Colin B. Macdonald" Date: Sat, 25 Feb 2023 15:56:06 -0800 Subject: [PATCH 07/19] Force default flow style to False I think this is for older pyyaml, AFAICT, False is the default circa 2022. No need to specific indent=2 as that seems to be the default. --- req2flatpak.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/req2flatpak.py b/req2flatpak.py index c17962d..bf741b3 100755 --- a/req2flatpak.py +++ b/req2flatpak.py @@ -652,7 +652,7 @@ def build_module_as_yaml_str(cls, *args, **kwargs) -> str: # optional dependency, not imported at top import yaml - return yaml.dump(cls.build_module(*args, **kwargs), indent=2) + return yaml.dump(cls.build_module(*args, **kwargs), default_flow_style=False) # ============================================================================= @@ -749,7 +749,7 @@ def main(): if options.platform_info: info = asdict(PlatformFactory.from_current_interpreter()) if options.yaml: - yaml.dump(info, output_stream, indent=2) + yaml.dump(info, output_stream, default_flow_style=False) else: json.dump(info, output_stream, indent=4) parser.exit() @@ -811,7 +811,7 @@ def main(): build_module = FlatpakGenerator.build_module(requirements, downloads) if options.yaml: - yaml.dump(build_module, output_stream, indent=2) + yaml.dump(build_module, output_stream, default_flow_style=False) parser.exit() # write output From 820c43bb4dd162c80127964f7046ea33653e9ac2 Mon Sep 17 00:00:00 2001 From: "Colin B. Macdonald" Date: Sat, 25 Feb 2023 16:24:39 -0800 Subject: [PATCH 08/19] Add test for yaml output --- tests/test_req2flatpak.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_req2flatpak.py b/tests/test_req2flatpak.py index 59e4292..d782cfe 100644 --- a/tests/test_req2flatpak.py +++ b/tests/test_req2flatpak.py @@ -9,6 +9,8 @@ from typing import List from unittest.mock import patch +import yaml + from req2flatpak import ( DownloadChooser, FlatpakGenerator, @@ -95,6 +97,15 @@ def test_cli_with_reqs_as_args(self): build_module = json.loads(result.stdout) self.validate_build_module(build_module) + def test_cli_with_reqs_as_args_yaml(self): + """Runs req2flatpak in yaml mode by passing requirements as cmdline arg.""" + args = ["--requirements"] + self.requirements + args += ["--target-platforms"] + self.target_platforms + args += ["--yaml"] + result = self._run_r2f(args) + build_module = yaml.load(result.stdout, yaml.Loader) + self.validate_build_module(build_module) + def test_cli_with_reqs_as_file(self): """Runs req2flatpak by passing requirements as requirements.txt file.""" with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8") as req_file: From df7bea2eb2e3ae9bed320c1c25e404e25eb25f22 Mon Sep 17 00:00:00 2001 From: "Colin B. Macdonald" Date: Sun, 26 Feb 2023 19:59:48 -0800 Subject: [PATCH 09/19] Add another yaml test for requirements file --- tests/test_req2flatpak.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_req2flatpak.py b/tests/test_req2flatpak.py index d782cfe..4364696 100644 --- a/tests/test_req2flatpak.py +++ b/tests/test_req2flatpak.py @@ -118,6 +118,19 @@ def test_cli_with_reqs_as_file(self): build_module = json.loads(result.stdout) self.validate_build_module(build_module) + def test_cli_with_reqs_as_file_yaml(self): + """Runs req2flatpak by passing requirements as requirements.txt file.""" + with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8") as req_file: + req_file.write("\n".join(self.requirements)) + req_file.flush() + req_file.seek(0) + args = ["--requirements-file", req_file.name] + args += ["--target-platforms"] + self.target_platforms + args += ["--yaml"] + result = self._run_r2f(args) + build_module = yaml.load(result.stdout, yaml.Loader) + self.validate_build_module(build_module) + def test_api(self): """Runs req2flatpak by calling its python api.""" platforms = [ From 126f9b88a5034e34753b966222037bfd62a9d3ea Mon Sep 17 00:00:00 2001 From: "Colin B. Macdonald" Date: Mon, 27 Feb 2023 10:55:56 -0800 Subject: [PATCH 10/19] test: use yaml safe_load which is simpler Also avoids warnings from linters. --- tests/test_req2flatpak.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_req2flatpak.py b/tests/test_req2flatpak.py index 4364696..0a3ce18 100644 --- a/tests/test_req2flatpak.py +++ b/tests/test_req2flatpak.py @@ -103,7 +103,7 @@ def test_cli_with_reqs_as_args_yaml(self): args += ["--target-platforms"] + self.target_platforms args += ["--yaml"] result = self._run_r2f(args) - build_module = yaml.load(result.stdout, yaml.Loader) + build_module = yaml.safe_load(result.stdout) self.validate_build_module(build_module) def test_cli_with_reqs_as_file(self): @@ -128,7 +128,7 @@ def test_cli_with_reqs_as_file_yaml(self): args += ["--target-platforms"] + self.target_platforms args += ["--yaml"] result = self._run_r2f(args) - build_module = yaml.load(result.stdout, yaml.Loader) + build_module = yaml.safe_load(result.stdout) self.validate_build_module(build_module) def test_api(self): From 377a4a4ef2183965dfe793315d3dcd8e619ace0e Mon Sep 17 00:00:00 2001 From: real-yfprojects Date: Tue, 28 Feb 2023 09:00:10 +0100 Subject: [PATCH 11/19] Fix extra dependency configuration and add pyyaml to lock file. * pyproject.toml (tool.poetry.dependencies): Add `pyyaml` as optional dependency. * poetry.lock : Run `poetry lock --no-update` to add pyyaml. --- poetry.lock | 5 +++-- pyproject.toml | 31 ++++++++++++++++--------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7f8abe1..e474c7d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -719,7 +719,7 @@ files = [ name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1230,8 +1230,9 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [extras] packaging = ["packaging"] +yaml = ["pyyaml"] [metadata] lock-version = "2.0" python-versions = "^3.7.2" -content-hash = "750c7f502c3eaec5a8d8349251f90fd9fcf577fb4185ca3716e0a54f21e96337" +content-hash = "8cf0740c1d625b42dc2f3bca5baab8d52c95a2fc60e71016460a5d5acfce85a0" diff --git a/pyproject.toml b/pyproject.toml index c63777a..ca8a453 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,25 +1,26 @@ [build-system] -requires = ["poetry-core"] +requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.poetry] -name = "req2flatpak" -version = "0.1.0" +name = "req2flatpak" +version = "0.1.0" description = "Generates a flatpak-builder build module for installing python packages defined in requirements.txt files." -authors = ["johannesjh "] -license = "MIT" -readme = "README.rst" +authors = ["johannesjh "] +license = "MIT" +readme = "README.rst" [tool.poetry.scripts] req2flatpak = 'req2flatpak:main' [tool.poetry.dependencies] -python = "^3.7.2" +python = "^3.7.2" packaging = { version = "^21.3", optional = true } +pyyaml = { version = "^6.0", optional = true } [tool.poetry.extras] packaging = ["packaging"] -yaml = ["pyyaml"] +yaml = ["pyyaml"] [tool.poetry.group.lint.dependencies] pylama = { extras = [ @@ -38,18 +39,18 @@ pydocstyle = "<6.2" optional = true [tool.poetry.group.docs.dependencies] -sphinx = "^5.3.0" -sphinx-argparse = "^0.3.2" +sphinx = "^5.3.0" +sphinx-argparse = "^0.3.2" sphinx-rtd-theme-github-versions = "^1.1" [tool.isort] -profile = "black" +profile = "black" skip_gitignore = true [tool.pylama] max_line_length = 88 -concurrent = true -linters = "pycodestyle,pydocstyle,pyflakes,pylint,eradicate,mypy" +concurrent = true +linters = "pycodestyle,pydocstyle,pyflakes,pylint,eradicate,mypy" [tool.pylama.linter.pycodestyle] ignore = "W503,E203,E501" @@ -64,8 +65,8 @@ ignore = ["D202", "D203", "D205", "D401", "D212"] [tool.pylint] format.max-line-length = 88 -main.disable = ["C0301"] # ignore line-too-long -basic.good-names = ["e", "f", "py"] +main.disable = ["C0301"] # ignore line-too-long +basic.good-names = ["e", "f", "py"] [tool.bandit] exclude_dirs = ['tests'] From f5ae4feee2fac3884d759b49aabeff4e846e7d54 Mon Sep 17 00:00:00 2001 From: real-yfprojects Date: Tue, 28 Feb 2023 12:30:48 +0100 Subject: [PATCH 12/19] Add `types-pyyaml` to lint dev packages. * pyproject.toml * poetry.lock --- poetry.lock | 14 +++++++++++++- pyproject.toml | 5 ++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index e474c7d..fa66278 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1094,6 +1094,18 @@ files = [ {file = "types_docutils-0.19.1.4-py3-none-any.whl", hash = "sha256:870d71f3663141f67a3c59d26d2c54a8c478c842208bb0b345fbf6036f49f561"}, ] +[[package]] +name = "types-pyyaml" +version = "6.0.12.8" +description = "Typing stubs for PyYAML" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-PyYAML-6.0.12.8.tar.gz", hash = "sha256:19304869a89d49af00be681e7b267414df213f4eb89634c4495fa62e8f942b9f"}, + {file = "types_PyYAML-6.0.12.8-py3-none-any.whl", hash = "sha256:5314a4b2580999b2ea06b2e5f9a7763d860d6e09cdf21c0e9561daa9cbd60178"}, +] + [[package]] name = "types-setuptools" version = "65.7.0.4" @@ -1235,4 +1247,4 @@ yaml = ["pyyaml"] [metadata] lock-version = "2.0" python-versions = "^3.7.2" -content-hash = "8cf0740c1d625b42dc2f3bca5baab8d52c95a2fc60e71016460a5d5acfce85a0" +content-hash = "c5b1d81dd25cb29f850a0b2a212ea42efd3afe44050829e25d70854669f82d45" diff --git a/pyproject.toml b/pyproject.toml index ca8a453..b770e7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,10 @@ pylama = { extras = [ "toml", ], version = "^8.4.1" } bandit = { extras = ["toml"], version = "^1.7.4" } -types-setuptools = "^65.5.0.2" # type stubs for mypy linting + +# type stubs for mypy linting +types-setuptools = "^65.5.0.2" +types-pyyaml = "^6.0.12.8" # [tool.poetry.group.tests.dependencies] pydocstyle = "<6.2" From b3ad6296e429e89c56e3804d90c2443aa9d30cb9 Mon Sep 17 00:00:00 2001 From: real-yfprojects Date: Tue, 28 Feb 2023 12:32:31 +0100 Subject: [PATCH 13/19] Run ci tests with multiple sets of extra dependencies. This adds a matrix that runs the tests once with `packaging`, `yaml` and both. * .github/workflows/ci.yml * tests/test_req2flatpak.py : Skip yaml tests when pyyaml isn't installed. --- .github/workflows/ci.yml | 5 ++++- tests/test_req2flatpak.py | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7df49c..aa21651 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,9 @@ jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + extras: ["packaging", "yaml", "packaging yaml"] steps: - name: Checkout uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 @@ -33,7 +36,7 @@ jobs: id: setup uses: ./.github/actions/setup with: - install-options: --without lint + install-options: --without lint --extras ${{matrix.extras}} - name: Run Tests run: make test diff --git a/tests/test_req2flatpak.py b/tests/test_req2flatpak.py index 0a3ce18..e557411 100644 --- a/tests/test_req2flatpak.py +++ b/tests/test_req2flatpak.py @@ -7,9 +7,13 @@ from itertools import product from pathlib import Path from typing import List +from unittest import skipUnless from unittest.mock import patch -import yaml +try: + import yaml +except ImportError: + yaml = None # type: ignore from req2flatpak import ( DownloadChooser, @@ -97,6 +101,7 @@ def test_cli_with_reqs_as_args(self): build_module = json.loads(result.stdout) self.validate_build_module(build_module) + @skipUnless(yaml, "The yaml extra dependency is needed for this feature.") def test_cli_with_reqs_as_args_yaml(self): """Runs req2flatpak in yaml mode by passing requirements as cmdline arg.""" args = ["--requirements"] + self.requirements @@ -118,6 +123,7 @@ def test_cli_with_reqs_as_file(self): build_module = json.loads(result.stdout) self.validate_build_module(build_module) + @skipUnless(yaml, "The yaml extra dependency is needed for this feature.") def test_cli_with_reqs_as_file_yaml(self): """Runs req2flatpak by passing requirements as requirements.txt file.""" with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8") as req_file: From de6c5a67769c5c08ab350c1fcbad15adfa003451 Mon Sep 17 00:00:00 2001 From: real-yfprojects Date: Tue, 28 Feb 2023 13:32:01 +0100 Subject: [PATCH 14/19] Generate test matrix dynamically for extras. * .github/workflows/ci.yml --- .github/workflows/ci.yml | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa21651..d78daf0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,11 +23,41 @@ jobs: - name: Run linters run: make lint + generate_test_matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Checkout + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + + - name: setup python + uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0 + with: + python-version: "3.11" + + - name: Extract extras from `pyproject.toml` + id: set-matrix + shell: python + run: | + import tomllib + import os + import json + with open('pyproject.toml', 'rb') as f: + manifest = tomllib.load(f) + yaml = { 'include' : [{ 'extras' : extra} for extra in [''] + list(manifest['tool']['poetry']['extras'])]} + out = json.dumps(yaml) + print(out) + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write('matrix=' + out) + test: + name: test ${{ matrix.extras && 'with' || '' }} ${{ matrix.extras }} runs-on: ubuntu-latest + needs: generate_test_matrix strategy: - matrix: - extras: ["packaging", "yaml", "packaging yaml"] + matrix: ${{ fromJson(needs.generate_test_matrix.outputs.matrix) }} + fail-fast: false steps: - name: Checkout uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 @@ -36,7 +66,7 @@ jobs: id: setup uses: ./.github/actions/setup with: - install-options: --without lint --extras ${{matrix.extras}} + install-options: --without lint ${{ matrix.extras && format('--extras "{0}"', matrix.extras) || '' }} - name: Run Tests run: make test From 0f42bc46211f1ac9e9453f51be51b172b0b5d121 Mon Sep 17 00:00:00 2001 From: real-yfprojects Date: Sat, 11 Mar 2023 19:53:09 +0100 Subject: [PATCH 15/19] Move import of `yaml` to the beginning of `req2flatpak.py`. Yaml is tried to be imported else `yaml` is set to `None`. Later the code will only check wether `yaml` is True (is not None). * req2flatpak.py --- req2flatpak.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/req2flatpak.py b/req2flatpak.py index bf741b3..e073df7 100755 --- a/req2flatpak.py +++ b/req2flatpak.py @@ -64,6 +64,10 @@ logger = logging.getLogger(__name__) +try: + import yaml +except ImportError: + yaml = None # type: ignore # ============================================================================= # Helper functions / semi vendored code @@ -73,7 +77,6 @@ # added with py 3.8 from functools import cached_property # type: ignore[attr-defined] except ImportError: - # Inspired by the implementation in the standard library # pylint: disable=invalid-name,too-few-public-methods class cached_property: # type: ignore[no-redef] @@ -551,9 +554,7 @@ def downloads( preferred downloads are returned first. """ cache = set() - for (platform_tag, download) in product( - platform.python_tags, release.downloads - ): + for platform_tag, download in product(platform.python_tags, release.downloads): if download in cache: continue if wheels_only and not download.is_wheel: @@ -650,7 +651,10 @@ def build_module_as_yaml_str(cls, *args, **kwargs) -> str: :py:meth:`~req2flatpak.FlatpakGenerator.build_module` """ # optional dependency, not imported at top - import yaml + if not yaml: + raise ImportError( + "Package `pyyaml` has to be installed for the yaml format." + ) return yaml.dump(cls.build_module(*args, **kwargs), default_flow_style=False) @@ -736,14 +740,10 @@ def main(): else: output_stream = sys.stdout - if options.yaml: - try: - # optional dependency, not imported at top - import yaml - except ImportError: - parser.error( - "Outputing YAML requires 'pyyaml' package: try 'pip install pyyaml'" - ) + if options.yaml and not yaml: + parser.error( + "Outputing YAML requires 'pyyaml' package: try 'pip install pyyaml'" + ) # print platform info if requested, and exit if options.platform_info: From d27d35e4e09d19fc189ad6f50d923c1710231bf5 Mon Sep 17 00:00:00 2001 From: real-yfprojects Date: Sat, 11 Mar 2023 19:56:04 +0100 Subject: [PATCH 16/19] Ignore too-many-branches in `main` function. * req2flatpak.py : Ignore pylint complain `too-many-branches` for `main`. --- req2flatpak.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/req2flatpak.py b/req2flatpak.py index e073df7..d3cf18e 100755 --- a/req2flatpak.py +++ b/req2flatpak.py @@ -725,7 +725,7 @@ def cli_parser() -> argparse.ArgumentParser: return parser -def main(): +def main(): # pylint: disable=too-many-branches """Main function that provides req2flatpak's commandline interface.""" # process commandline arguments From 9bb6055fb2b23716e372cac1366e91189fa7b3ec Mon Sep 17 00:00:00 2001 From: real-yfprojects Date: Sat, 11 Mar 2023 20:05:10 +0100 Subject: [PATCH 17/19] Use a common contextmanager to create requirements file in tests. * tests/test_req2flatpak.py (Req2FlatpakBaseTest.requirements_file): Implement this method returning a contextmanager that creates and prefills a requirements file with requirements. * tests/test_req2flatpak.py (Req2FlatpakBaseTest.test_cli_with_reqs_as_file[_yaml]): Use `requirements_file()`. --- tests/test_req2flatpak.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/test_req2flatpak.py b/tests/test_req2flatpak.py index e557411..0b15008 100644 --- a/tests/test_req2flatpak.py +++ b/tests/test_req2flatpak.py @@ -4,9 +4,10 @@ import tempfile import unittest from abc import ABC +from contextlib import contextmanager from itertools import product from pathlib import Path -from typing import List +from typing import Generator, List from unittest import skipUnless from unittest.mock import patch @@ -87,6 +88,17 @@ def validate_build_module(self, build_module: dict) -> None: """To be implemented by subclasses.""" raise NotImplementedError + @contextmanager + def requirements_file( + self, + ) -> Generator[tempfile._TemporaryFileWrapper[str], None, None]: + """Create a temporary requirements file.""" + with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8") as req_file: + req_file.write("\n".join(self.requirements)) + req_file.flush() + req_file.seek(0) + yield req_file + def _run_r2f(self, args: List[str]) -> subprocess.CompletedProcess: """Runs req2flatpak's cli in a subprocess.""" cwd = Path(__file__).parent / ".." @@ -113,10 +125,7 @@ def test_cli_with_reqs_as_args_yaml(self): def test_cli_with_reqs_as_file(self): """Runs req2flatpak by passing requirements as requirements.txt file.""" - with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8") as req_file: - req_file.write("\n".join(self.requirements)) - req_file.flush() - req_file.seek(0) + with self.requirements_file() as req_file: args = ["--requirements-file", req_file.name] args += ["--target-platforms"] + self.target_platforms result = self._run_r2f(args) @@ -126,10 +135,7 @@ def test_cli_with_reqs_as_file(self): @skipUnless(yaml, "The yaml extra dependency is needed for this feature.") def test_cli_with_reqs_as_file_yaml(self): """Runs req2flatpak by passing requirements as requirements.txt file.""" - with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8") as req_file: - req_file.write("\n".join(self.requirements)) - req_file.flush() - req_file.seek(0) + with self.requirements_file() as req_file: args = ["--requirements-file", req_file.name] args += ["--target-platforms"] + self.target_platforms args += ["--yaml"] From 6d9303d26df0ad351387633832468fa20f42c434 Mon Sep 17 00:00:00 2001 From: real-yfprojects Date: Sat, 11 Mar 2023 20:15:00 +0100 Subject: [PATCH 18/19] Use `FlatpakGenerator.build_module_as_[yaml_]str` in `main`. These methods implement formatting, so why duplicate that code. Fixes #45. * req2flatpak.py (main) --- req2flatpak.py | 16 +++++++++------- tests/test_req2flatpak.py | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/req2flatpak.py b/req2flatpak.py index d3cf18e..ff6799b 100755 --- a/req2flatpak.py +++ b/req2flatpak.py @@ -808,14 +808,16 @@ def main(): # pylint: disable=too-many-branches } # generate flatpak-builder build module - build_module = FlatpakGenerator.build_module(requirements, downloads) - if options.yaml: - yaml.dump(build_module, output_stream, default_flow_style=False) - parser.exit() - - # write output - json.dump(build_module, output_stream, indent=4) + # write yaml + output_stream.write( + FlatpakGenerator.build_module_as_yaml_str(requirements, downloads) + ) + else: + # write json + output_stream.write( + FlatpakGenerator.build_module_as_str(requirements, downloads) + ) if __name__ == "__main__": diff --git a/tests/test_req2flatpak.py b/tests/test_req2flatpak.py index 0b15008..ead2a7f 100644 --- a/tests/test_req2flatpak.py +++ b/tests/test_req2flatpak.py @@ -91,7 +91,7 @@ def validate_build_module(self, build_module: dict) -> None: @contextmanager def requirements_file( self, - ) -> Generator[tempfile._TemporaryFileWrapper[str], None, None]: + ) -> Generator[tempfile._TemporaryFileWrapper, None, None]: """Create a temporary requirements file.""" with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8") as req_file: req_file.write("\n".join(self.requirements)) From ed8692109d7389eadda0e495ac5e298de473868b Mon Sep 17 00:00:00 2001 From: "Colin B. Macdonald" Date: Thu, 16 Mar 2023 20:47:25 -0700 Subject: [PATCH 19/19] Fix dict-ordering when outputing yaml --- req2flatpak.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/req2flatpak.py b/req2flatpak.py index ff6799b..a82c596 100755 --- a/req2flatpak.py +++ b/req2flatpak.py @@ -656,7 +656,9 @@ def build_module_as_yaml_str(cls, *args, **kwargs) -> str: "Package `pyyaml` has to be installed for the yaml format." ) - return yaml.dump(cls.build_module(*args, **kwargs), default_flow_style=False) + return yaml.dump( + cls.build_module(*args, **kwargs), default_flow_style=False, sort_keys=False + ) # ============================================================================= @@ -749,7 +751,7 @@ def main(): # pylint: disable=too-many-branches if options.platform_info: info = asdict(PlatformFactory.from_current_interpreter()) if options.yaml: - yaml.dump(info, output_stream, default_flow_style=False) + yaml.dump(info, output_stream, default_flow_style=False, sort_keys=False) else: json.dump(info, output_stream, indent=4) parser.exit()