req2flatpak 0.2.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ Copyright 2022 johannesjh
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,21 +1,22 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: req2flatpak
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: Generates a flatpak-builder build module for installing python packages defined in requirements.txt files.
5
5
  License: MIT
6
6
  Author: johannesjh
7
7
  Author-email: johannesjh@users.noreply.github.com
8
- Requires-Python: >=3.7.2,<4.0.0
8
+ Requires-Python: >=3.8
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.8
12
12
  Classifier: Programming Language :: Python :: 3.9
13
13
  Classifier: Programming Language :: Python :: 3.10
14
14
  Classifier: Programming Language :: Python :: 3.11
15
- Provides-Extra: packaging
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
16
17
  Provides-Extra: yaml
17
- Requires-Dist: packaging (>=21.3,<22.0) ; extra == "packaging"
18
- Requires-Dist: pyyaml (>=6.0,<7.0) ; extra == "yaml"
18
+ Requires-Dist: packaging (>=21.3,<22.0)
19
+ Requires-Dist: pyyaml (>=6.0.1,<7.0.0) ; extra == "yaml"
19
20
  Description-Content-Type: text/x-rst
20
21
 
21
22
  req2flatpak
@@ -45,15 +46,19 @@ using pip.
45
46
  Getting Started
46
47
  ---------------
47
48
 
48
- Run ``pip install req2flatpak``
49
- to install the latest release of req2flatpak.
49
+ Install req2flatpak using pip (or any other python package installer),
50
+ e.g., by running ``pip install req2flatpak``.
50
51
 
51
- It is possible to use req2flatpak from the commandline,
52
- as well as programmatically from a python script.
52
+ Prepare a ``requirements.txt`` file with the packages that you want to install as part of a flatpak build.
53
+ You need to specify exact versions for all the packages including their dependencies.
54
+ (Various tools exist to help creating such a requirements file with fully resolved dependencies and frozen package versions;
55
+ for example, you can use
56
+ `pip-compile <https://pypi.org/project/pip-tools/>`_ or
57
+ `poetry export <https://pypi.org/project/poetry-plugin-export/>`_ to export a suitable ``requirements.txt`` file).
53
58
 
54
- Commandline usage means you can invoke req2flatpak’s commandline interface
55
- as follows, in order to generate a ``flatpak-builder`` build module
56
- from given python package requirements:
59
+ Run req2flatpak.
60
+ For example, run this command to generate a ``flatpak-builder`` build module
61
+ from your ``requirements.txt`` file:
57
62
 
58
63
  .. code:: bash
59
64
 
@@ -66,17 +71,17 @@ choose appropriate downloads for the specified target platforms,
66
71
  and generate a flatpak-builder build module.
67
72
  The module, if included in a flatpak-builder build manifest,
68
73
  will install the required packages using pip.
69
-
70
74
  The commandline option to define target platforms uses the format ``<pythonversion>-<architecture>``.
71
75
  To learn more about available commandline options,
72
76
  run ``req2flatpak.py --help``.
73
77
 
74
- Programmatic usage is also possible.
75
- This means you can invoke functionality from req2flatpak in your own python script,
76
- allowing you to tweak the desired behavior in many ways.
77
- The `documentation <https://johannesjh.github.io/req2flatpak/>`__
78
- describes req2flatpak's python api and includes code examples
79
- to help you get started quickly.
78
+ Note that programmatic usage of req2flatpak is also possible.
79
+ This means you can write a python script to invoke req2flatpak,
80
+ which allows to tweak the desired behavior in many ways.
81
+
82
+ Go read the `documentation <https://johannesjh.github.io/req2flatpak/>`__
83
+ to learn more about req2flatpak's commandline and Python APIs.
84
+ The documentation includes further examples to help you get started quickly.
80
85
 
81
86
 
82
87
  Documentation
@@ -25,15 +25,19 @@ using pip.
25
25
  Getting Started
26
26
  ---------------
27
27
 
28
- Run ``pip install req2flatpak``
29
- to install the latest release of req2flatpak.
28
+ Install req2flatpak using pip (or any other python package installer),
29
+ e.g., by running ``pip install req2flatpak``.
30
30
 
31
- It is possible to use req2flatpak from the commandline,
32
- as well as programmatically from a python script.
31
+ Prepare a ``requirements.txt`` file with the packages that you want to install as part of a flatpak build.
32
+ You need to specify exact versions for all the packages including their dependencies.
33
+ (Various tools exist to help creating such a requirements file with fully resolved dependencies and frozen package versions;
34
+ for example, you can use
35
+ `pip-compile <https://pypi.org/project/pip-tools/>`_ or
36
+ `poetry export <https://pypi.org/project/poetry-plugin-export/>`_ to export a suitable ``requirements.txt`` file).
33
37
 
34
- Commandline usage means you can invoke req2flatpak’s commandline interface
35
- as follows, in order to generate a ``flatpak-builder`` build module
36
- from given python package requirements:
38
+ Run req2flatpak.
39
+ For example, run this command to generate a ``flatpak-builder`` build module
40
+ from your ``requirements.txt`` file:
37
41
 
38
42
  .. code:: bash
39
43
 
@@ -46,17 +50,17 @@ choose appropriate downloads for the specified target platforms,
46
50
  and generate a flatpak-builder build module.
47
51
  The module, if included in a flatpak-builder build manifest,
48
52
  will install the required packages using pip.
49
-
50
53
  The commandline option to define target platforms uses the format ``<pythonversion>-<architecture>``.
51
54
  To learn more about available commandline options,
52
55
  run ``req2flatpak.py --help``.
53
56
 
54
- Programmatic usage is also possible.
55
- This means you can invoke functionality from req2flatpak in your own python script,
56
- allowing you to tweak the desired behavior in many ways.
57
- The `documentation <https://johannesjh.github.io/req2flatpak/>`__
58
- describes req2flatpak's python api and includes code examples
59
- to help you get started quickly.
57
+ Note that programmatic usage of req2flatpak is also possible.
58
+ This means you can write a python script to invoke req2flatpak,
59
+ which allows to tweak the desired behavior in many ways.
60
+
61
+ Go read the `documentation <https://johannesjh.github.io/req2flatpak/>`__
62
+ to learn more about req2flatpak's commandline and Python APIs.
63
+ The documentation includes further examples to help you get started quickly.
60
64
 
61
65
 
62
66
  Documentation
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["poetry-core"]
3
+ build-backend = "poetry.core.masonry.api"
4
+
5
+ [tool.poetry]
6
+ name = "req2flatpak"
7
+ version = "0.3.1"
8
+ description = "Generates a flatpak-builder build module for installing python packages defined in requirements.txt files."
9
+ authors = ["johannesjh <johannesjh@users.noreply.github.com>"]
10
+ license = "MIT"
11
+ readme = ["README.rst", "docs/source/changelog.rst"]
12
+
13
+ [tool.poetry.scripts]
14
+ req2flatpak = 'req2flatpak:main'
15
+
16
+ [tool.poetry.dependencies]
17
+ python = ">=3.8"
18
+ packaging = { version = "^21.3" }
19
+ pyyaml = { version = "^6.0.1", optional = true }
20
+
21
+ [tool.poetry.extras]
22
+ yaml = ["pyyaml"]
23
+
24
+ [tool.poetry.group.lint.dependencies]
25
+ ruff = "^0.11.2"
26
+
27
+ # type stubs for mypy linting
28
+ types-setuptools = "^65.5.0.2"
29
+ types-pyyaml = "^6.0.12.8"
30
+
31
+ # [tool.poetry.group.tests.dependencies]
32
+ pydocstyle = "<6.2"
33
+
34
+ [tool.poetry.group.docs]
35
+ optional = true
36
+
37
+ [tool.poetry.group.docs.dependencies]
38
+ sphinx = "^6.2.1"
39
+ sphinx-argparse = "^0.4.0"
40
+ sphinx-rtd-theme = "^2.0.0"
41
+ sphinx-rtd-theme-github-versions = "^1.1"
42
+
43
+ [tool.bandit]
44
+ exclude_dirs = ['tests']
@@ -2,7 +2,7 @@
2
2
 
3
3
  # req2flatpak is MIT-licensed.
4
4
  #
5
- # Copyright 2022 johannesjh
5
+ # Copyright 2022-2024 johannesjh
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"), to deal
@@ -43,11 +43,11 @@ import sys
43
43
  import urllib.request
44
44
  from contextlib import nullcontext, suppress
45
45
  from dataclasses import asdict, dataclass, field
46
+ from importlib import metadata
46
47
  from itertools import product
47
48
  from typing import (
48
49
  Any,
49
50
  Dict,
50
- FrozenSet,
51
51
  Generator,
52
52
  Hashable,
53
53
  Iterable,
@@ -60,7 +60,9 @@ from typing import (
60
60
  )
61
61
  from urllib.parse import urlparse
62
62
 
63
- import pkg_resources
63
+ import packaging.requirements as packaging_reqs
64
+ import packaging.tags
65
+ from packaging.utils import parse_wheel_filename
64
66
 
65
67
  logger = logging.getLogger(__name__)
66
68
 
@@ -109,64 +111,14 @@ class InvalidWheelFilename(Exception):
109
111
  """An invalid wheel filename was found, users should refer to PEP 427."""
110
112
 
111
113
 
112
- try:
113
- # use packaging.tags functionality if available
114
- from packaging.utils import parse_wheel_filename
115
-
116
- def tags_from_wheel_filename(filename: str) -> Set[str]:
117
- """
118
- Parses a wheel filename into a list of compatible platform tags.
119
-
120
- Implemented using functionality from ``packaging.utils.parse_wheel_filename``.
121
- """
122
- _, _, _, tags = parse_wheel_filename(filename)
123
- return {str(tag) for tag in tags}
124
-
125
- except ModuleNotFoundError:
126
- # fall back to a local implementation
127
- # that is heavily inspired by / almost vendored from the `packaging` package:
128
- def tags_from_wheel_filename(filename: str) -> Set[str]:
129
- """
130
- Parses a wheel filename into a list of compatible platform tags.
131
-
132
- Implemented as (semi-)vendored functionality in req2flatpak.
133
- """
134
- Tag = Tuple[str, str, str]
135
-
136
- # the following code is based on packaging.tags.parse_tag,
137
- # it is needed for the parse_wheel_filename function:
138
- def parse_tag(tag: str) -> FrozenSet[Tag]:
139
- tags: Set[Tag] = set()
140
- interpreters, abis, platforms = tag.split("-")
141
- for interpreter in interpreters.split("."):
142
- for abi in abis.split("."):
143
- for platform_ in platforms.split("."):
144
- tags.add((interpreter, abi, platform_))
145
- return frozenset(tags)
146
-
147
- # the following code is based on packaging.utils.parse_wheel_filename:
148
- # pylint: disable=redefined-outer-name
149
- def parse_wheel_filename(
150
- wheel_filename: str,
151
- ) -> Iterable[Tag]:
152
- if not wheel_filename.endswith(".whl"):
153
- raise InvalidWheelFilename(
154
- "Error parsing wheel filename: "
155
- "Invalid wheel filename (extension must be '.whl'): "
156
- f"{wheel_filename}"
157
- )
158
- wheel_filename = wheel_filename[:-4]
159
- dashes = wheel_filename.count("-")
160
- if dashes not in (4, 5):
161
- raise InvalidWheelFilename(
162
- "Error parsing wheel filename: "
163
- "Invalid wheel filename (wrong number of parts): "
164
- f"{wheel_filename}"
165
- )
166
- parts = wheel_filename.split("-", dashes - 2)
167
- return parse_tag(parts[-1])
114
+ def tags_from_wheel_filename(filename: str) -> Set[str]:
115
+ """
116
+ Parses a wheel filename into a list of compatible platform tags.
168
117
 
169
- return {"-".join(tag_tuple) for tag_tuple in parse_wheel_filename(filename)}
118
+ Implemented using functionality from ``packaging.utils.parse_wheel_filename``.
119
+ """
120
+ _, _, _, tags = parse_wheel_filename(filename)
121
+ return {str(tag) for tag in tags}
170
122
 
171
123
 
172
124
  # =============================================================================
@@ -271,17 +223,8 @@ class PlatformFactory:
271
223
 
272
224
  @staticmethod
273
225
  def _get_current_python_tags() -> List[str]:
274
- try:
275
- # pylint: disable=import-outside-toplevel
276
- import packaging.tags
277
-
278
- tags = [str(tag) for tag in packaging.tags.sys_tags()]
279
- return tags
280
- except ModuleNotFoundError as e:
281
- logger.warning(
282
- 'Error trying to import the "packaging" package.', exc_info=e
283
- )
284
- return []
226
+ tags = [str(tag) for tag in packaging.tags.sys_tags()]
227
+ return tags
285
228
 
286
229
  @classmethod
287
230
  def from_current_interpreter(cls) -> Platform:
@@ -431,27 +374,32 @@ class RequirementsParser:
431
374
  resolve dependencies.
432
375
  """
433
376
 
434
- # based on: https://stackoverflow.com/a/59971236
435
- # using functionality from pkg_resources.parse_requirements
436
-
437
377
  @classmethod
438
378
  def parse_string(cls, requirements_txt: str) -> List[Requirement]:
439
379
  """Parses requirements.txt string content into a list of Requirement objects."""
440
380
 
441
- def validate_requirement(req: pkg_resources.Requirement) -> None:
442
- assert (
443
- len(req.specs) == 1
444
- ), "Error parsing requirements: A single version number must be specified."
445
- assert (
446
- req.specs[0][0] == "=="
447
- ), "Error parsing requirements: The exact version must specified as 'package==version'."
381
+ def validate_requirement(req: packaging_reqs.Requirement) -> None:
382
+ assert len(req.specifier) == 1, (
383
+ "Error parsing requirements: A single version number must be specified."
384
+ )
385
+ assert list(req.specifier)[0].operator == "==", (
386
+ "Error parsing requirements: The exact version must specified as 'package==version'."
387
+ )
448
388
 
449
- def make_requirement(req: pkg_resources.Requirement) -> Requirement:
389
+ def make_requirement(req: packaging_reqs.Requirement) -> Requirement:
450
390
  validate_requirement(req)
451
- return Requirement(package=req.project_name, version=req.specs[0][1])
391
+ return Requirement(package=req.name, version=list(req.specifier)[0].version)
452
392
 
453
- reqs = pkg_resources.parse_requirements(requirements_txt)
454
- return [make_requirement(req) for req in reqs]
393
+ requirements = []
394
+ for line in requirements_txt.splitlines():
395
+ if not (line := line.strip()):
396
+ continue
397
+ if line.startswith("#"):
398
+ continue
399
+ req = packaging_reqs.Requirement(line)
400
+ requirements.append(make_requirement(req))
401
+
402
+ return requirements
455
403
 
456
404
  @classmethod
457
405
  def parse_file(cls, file) -> List[Requirement]:
@@ -502,6 +450,8 @@ class PypiClient:
502
450
  url = f"https://pypi.org/pypi/{req.package}/{req.version}/json"
503
451
  json_string = cls._query(url)
504
452
  json_dict = json.loads(json_string)
453
+ if not json_dict["urls"]:
454
+ raise RuntimeError(f"Release {req} has no URLs: maybe too old?")
505
455
  return Release(
506
456
  package=req.package,
507
457
  version=req.version,
@@ -599,14 +549,18 @@ class DownloadChooser:
599
549
  class FlatpakGenerator:
600
550
  """Provides methods for generating a flatpak-builder build module."""
601
551
 
552
+ pip_install_template: str = (
553
+ "pip3 install --verbose --exists-action=i "
554
+ '--no-index --find-links="file://${PWD}" '
555
+ "--prefix=${FLATPAK_DEST} --no-build-isolation "
556
+ )
557
+
602
558
  @staticmethod
603
559
  def build_module(
604
560
  requirements: Iterable[Requirement],
605
561
  downloads: Iterable[Download],
606
562
  module_name="python3-package-installation",
607
- pip_install_template: str = "pip3 install --verbose --exists-action=i "
608
- '--no-index --find-links="file://${PWD}" '
609
- "--prefix=${FLATPAK_DEST} --no-build-isolation ",
563
+ pip_install_template: str = pip_install_template,
610
564
  ) -> dict:
611
565
  """Generates a build module for inclusion in a flatpak-builder build manifest."""
612
566
 
@@ -759,7 +713,9 @@ def main(): # pylint: disable=too-many-branches
759
713
  # print installed packages if requested, and exit
760
714
  if options.installed_packages:
761
715
  # pylint: disable=not-an-iterable
762
- pkgs = {p.key: p.version for p in pkg_resources.working_set}
716
+ pkgs = {
717
+ p.metadata["Name"]: p.metadata["Version"] for p in metadata.distributions()
718
+ }
763
719
  for pkg, version in pkgs.items():
764
720
  print(f"{pkg}=={version}", file=output_stream)
765
721
  parser.exit()
@@ -1,76 +0,0 @@
1
- [build-system]
2
- requires = ["poetry-core"]
3
- build-backend = "poetry.core.masonry.api"
4
-
5
- [tool.poetry]
6
- name = "req2flatpak"
7
- version = "0.2.0"
8
- description = "Generates a flatpak-builder build module for installing python packages defined in requirements.txt files."
9
- authors = ["johannesjh <johannesjh@users.noreply.github.com>"]
10
- license = "MIT"
11
- readme = ["README.rst", "docs/source/changelog.rst"]
12
-
13
- [tool.poetry.scripts]
14
- req2flatpak = 'req2flatpak:main'
15
-
16
- [tool.poetry.dependencies]
17
- python = "^3.7.2"
18
- packaging = { version = "^21.3", optional = true }
19
- pyyaml = { version = "^6.0", optional = true }
20
-
21
- [tool.poetry.extras]
22
- packaging = ["packaging"]
23
- yaml = ["pyyaml"]
24
-
25
- [tool.poetry.group.lint.dependencies]
26
- pylama = { extras = [
27
- "mypy",
28
- "pylint",
29
- "eradicate",
30
- "toml",
31
- ], version = "^8.4.1" }
32
- bandit = { extras = ["toml"], version = "^1.7.4" }
33
-
34
- # type stubs for mypy linting
35
- types-setuptools = "^65.5.0.2"
36
- types-pyyaml = "^6.0.12.8"
37
-
38
- # [tool.poetry.group.tests.dependencies]
39
- pydocstyle = "<6.2"
40
-
41
- [tool.poetry.group.docs]
42
- optional = true
43
-
44
- [tool.poetry.group.docs.dependencies]
45
- sphinx = "^5.3.0"
46
- sphinx-argparse = "^0.4.0"
47
- sphinx-rtd-theme = "^1.2.0"
48
- sphinx-rtd-theme-github-versions = "^1.1"
49
-
50
- [tool.isort]
51
- profile = "black"
52
- skip_gitignore = true
53
-
54
- [tool.pylama]
55
- max_line_length = 88
56
- concurrent = true
57
- linters = "pycodestyle,pydocstyle,pyflakes,pylint,eradicate,mypy"
58
-
59
- [tool.pylama.linter.pycodestyle]
60
- ignore = "W503,E203,E501"
61
-
62
- # [tool.pycodestyle] -> see setup.cfg
63
-
64
- [tool.pylama.linter.pydocstyle]
65
- ignore = "D202,D203,D205,D401,D212"
66
-
67
- [tool.pydocstyle]
68
- ignore = ["D202", "D203", "D205", "D401", "D212"]
69
-
70
- [tool.pylint]
71
- format.max-line-length = 88
72
- main.disable = ["C0301"] # ignore line-too-long
73
- basic.good-names = ["e", "f", "py"]
74
-
75
- [tool.bandit]
76
- exclude_dirs = ['tests']