req2flatpak 0.1.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,18 +1,22 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: req2flatpak
3
- Version: 0.1.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
- Provides-Extra: packaging
15
- Requires-Dist: packaging (>=21.3,<22.0); extra == "packaging"
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Provides-Extra: yaml
18
+ Requires-Dist: packaging (>=21.3,<22.0)
19
+ Requires-Dist: pyyaml (>=6.0.1,<7.0.0) ; extra == "yaml"
16
20
  Description-Content-Type: text/x-rst
17
21
 
18
22
  req2flatpak
@@ -42,15 +46,19 @@ using pip.
42
46
  Getting Started
43
47
  ---------------
44
48
 
45
- Run ``pip install git+https://github.com/johannesjh/req2flatpak``
46
- to install the latest development version of req2flatpak.
49
+ Install req2flatpak using pip (or any other python package installer),
50
+ e.g., by running ``pip install req2flatpak``.
47
51
 
48
- It is possible to use req2flatpak from the commandline,
49
- 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).
50
58
 
51
- Commandline usage means you can invoke req2flatpak’s commandline interface
52
- as follows, in order to generate a ``flatpak-builder`` build module
53
- 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:
54
62
 
55
63
  .. code:: bash
56
64
 
@@ -63,17 +71,17 @@ choose appropriate downloads for the specified target platforms,
63
71
  and generate a flatpak-builder build module.
64
72
  The module, if included in a flatpak-builder build manifest,
65
73
  will install the required packages using pip.
66
-
67
74
  The commandline option to define target platforms uses the format ``<pythonversion>-<architecture>``.
68
75
  To learn more about available commandline options,
69
76
  run ``req2flatpak.py --help``.
70
77
 
71
- Programmatic usage is also possible.
72
- This means you can invoke functionality from req2flatpak in your own python script,
73
- allowing you to tweak the desired behavior in many ways.
74
- The `documentation <https://johannesjh.github.io/req2flatpak/>`__
75
- describes req2flatpak's python api and includes code examples
76
- 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.
77
85
 
78
86
 
79
87
  Documentation
@@ -113,28 +121,34 @@ A comparison will likely change over time.
113
121
  As in Oct, 2022, in my personal opinion (johannesjh),
114
122
  I see the following similarities and differences:
115
123
 
116
- - Both projects generate build modules for flatpak-builder.
117
- - Both projects consist of a single script file with minimal
124
+ * Both projects generate build modules for flatpak-builder.
125
+
126
+ * Both projects consist of a single script file with minimal
118
127
  dependencies, and are thus very easy to install.
119
- - ``flatpak-pip-generator`` resolves dependencies and freezes
128
+
129
+ * ``flatpak-pip-generator`` resolves dependencies and freezes
120
130
  dependency versions, whereas ``req2flatpak.py`` asks the user to
121
131
  provide a fully resolved list of dependencies with frozen dependency
122
132
  versions. Various tools exist which make this easy, e.g.,
123
133
  pip, pip-compile and poetry.
124
- - ``flatpak-pip-generator`` is older and thus likely to be more mature.
134
+
135
+ * ``flatpak-pip-generator`` is older and thus likely to be more mature.
125
136
  It supports more commandline options and probably has a more complete
126
137
  feature set.
127
- - ``req2flatpak.py`` is faster. The script itself runs faster because
138
+
139
+ * ``req2flatpak.py`` is faster. The script itself runs faster because
128
140
  it does not need to download package files in order to generate the
129
141
  build module. And the flatpak build runs faster because all packages
130
142
  (from the entire ``requirements.txt`` file) are installed in a single
131
143
  call to ``pip install``.
132
- - ``req2flatpak.py`` re-implements some functionality of pip. In
144
+
145
+ * ``req2flatpak.py`` re-implements some functionality of pip. In
133
146
  contrast, ``flatpak-pip-generator`` uses pip’s official
134
147
  functionality. Specifically, ``req2flatpak.py`` re-implements how pip
135
148
  queries available downloads from pypi and how pip chooses suitable
136
149
  downloads to match a given target platform.
137
- - ``req2flatpak.py`` prefers binary wheels, whereas
150
+
151
+ * ``req2flatpak.py`` prefers binary wheels, whereas
138
152
  ``flatpak-pip-generator`` prefers source packages.
139
153
 
140
154
  License
@@ -142,3 +156,38 @@ License
142
156
 
143
157
  req2flatpak is MIT-licensed, see the ``COPYING`` file.
144
158
 
159
+ Changelog
160
+ =========
161
+
162
+ ..
163
+ This changelog is written by hand.
164
+ Newest releases are added to the top of the file.
165
+
166
+
167
+ v0.2 (2023-06-04) "Yaml"
168
+ ------------------------
169
+
170
+ Highlight: req2flatpak can now generate yaml output.
171
+
172
+ Features:
173
+
174
+ - Adds yaml output.
175
+ It is now possible to generate yaml output by specifying a ``.yaml`` file extension
176
+ or by specifying the ``--yaml`` commandline option.
177
+
178
+ Bugfixes:
179
+
180
+ - Fixes sorting when architecture is None - `#34 <https://github.com/johannesjh/req2flatpak/pull/34>`_
181
+ - Fixes exception handling with invalid platforms - `#39 <https://github.com/johannesjh/req2flatpak/pull/39>`_
182
+
183
+
184
+ v0.1 (2022-12-23) "Initial Release"
185
+ -----------------------------------
186
+
187
+ Highlight: This is the initial release of req2flatpak.
188
+
189
+ Features:
190
+
191
+ - Generates a flatpak-builder build module for given python package requirements; the module will install the required packages as part of a flatpak build.
192
+ - This initial release already comes with documentation, a clean code style, and automated tests that are run using continuous integration.
193
+
@@ -25,15 +25,19 @@ using pip.
25
25
  Getting Started
26
26
  ---------------
27
27
 
28
- Run ``pip install git+https://github.com/johannesjh/req2flatpak``
29
- to install the latest development version 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
@@ -96,28 +100,34 @@ A comparison will likely change over time.
96
100
  As in Oct, 2022, in my personal opinion (johannesjh),
97
101
  I see the following similarities and differences:
98
102
 
99
- - Both projects generate build modules for flatpak-builder.
100
- - Both projects consist of a single script file with minimal
103
+ * Both projects generate build modules for flatpak-builder.
104
+
105
+ * Both projects consist of a single script file with minimal
101
106
  dependencies, and are thus very easy to install.
102
- - ``flatpak-pip-generator`` resolves dependencies and freezes
107
+
108
+ * ``flatpak-pip-generator`` resolves dependencies and freezes
103
109
  dependency versions, whereas ``req2flatpak.py`` asks the user to
104
110
  provide a fully resolved list of dependencies with frozen dependency
105
111
  versions. Various tools exist which make this easy, e.g.,
106
112
  pip, pip-compile and poetry.
107
- - ``flatpak-pip-generator`` is older and thus likely to be more mature.
113
+
114
+ * ``flatpak-pip-generator`` is older and thus likely to be more mature.
108
115
  It supports more commandline options and probably has a more complete
109
116
  feature set.
110
- - ``req2flatpak.py`` is faster. The script itself runs faster because
117
+
118
+ * ``req2flatpak.py`` is faster. The script itself runs faster because
111
119
  it does not need to download package files in order to generate the
112
120
  build module. And the flatpak build runs faster because all packages
113
121
  (from the entire ``requirements.txt`` file) are installed in a single
114
122
  call to ``pip install``.
115
- - ``req2flatpak.py`` re-implements some functionality of pip. In
123
+
124
+ * ``req2flatpak.py`` re-implements some functionality of pip. In
116
125
  contrast, ``flatpak-pip-generator`` uses pip’s official
117
126
  functionality. Specifically, ``req2flatpak.py`` re-implements how pip
118
127
  queries available downloads from pypi and how pip chooses suitable
119
128
  downloads to match a given target platform.
120
- - ``req2flatpak.py`` prefers binary wheels, whereas
129
+
130
+ * ``req2flatpak.py`` prefers binary wheels, whereas
121
131
  ``flatpak-pip-generator`` prefers source packages.
122
132
 
123
133
  License
@@ -0,0 +1,34 @@
1
+ Changelog
2
+ =========
3
+
4
+ ..
5
+ This changelog is written by hand.
6
+ Newest releases are added to the top of the file.
7
+
8
+
9
+ v0.2 (2023-06-04) "Yaml"
10
+ ------------------------
11
+
12
+ Highlight: req2flatpak can now generate yaml output.
13
+
14
+ Features:
15
+
16
+ - Adds yaml output.
17
+ It is now possible to generate yaml output by specifying a ``.yaml`` file extension
18
+ or by specifying the ``--yaml`` commandline option.
19
+
20
+ Bugfixes:
21
+
22
+ - Fixes sorting when architecture is None - `#34 <https://github.com/johannesjh/req2flatpak/pull/34>`_
23
+ - Fixes exception handling with invalid platforms - `#39 <https://github.com/johannesjh/req2flatpak/pull/39>`_
24
+
25
+
26
+ v0.1 (2022-12-23) "Initial Release"
27
+ -----------------------------------
28
+
29
+ Highlight: This is the initial release of req2flatpak.
30
+
31
+ Features:
32
+
33
+ - Generates a flatpak-builder build module for given python package requirements; the module will install the required packages as part of a flatpak build.
34
+ - This initial release already comes with documentation, a clean code style, and automated tests that are run using continuous integration.
@@ -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,10 +60,16 @@ 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
 
69
+ try:
70
+ import yaml
71
+ except ImportError:
72
+ yaml = None # type: ignore
67
73
 
68
74
  # =============================================================================
69
75
  # Helper functions / semi vendored code
@@ -73,7 +79,6 @@ try:
73
79
  # added with py 3.8
74
80
  from functools import cached_property # type: ignore[attr-defined]
75
81
  except ImportError:
76
-
77
82
  # Inspired by the implementation in the standard library
78
83
  # pylint: disable=invalid-name,too-few-public-methods
79
84
  class cached_property: # type: ignore[no-redef]
@@ -102,65 +107,18 @@ except ImportError:
102
107
  return val
103
108
 
104
109
 
105
- try:
106
- # use packaging.tags functionality if available
107
- from packaging.utils import parse_wheel_filename
108
-
109
- def tags_from_wheel_filename(filename: str) -> Set[str]:
110
- """
111
- Parses a wheel filename into a list of compatible platform tags.
112
-
113
- Implemented using functionality from ``packaging.utils.parse_wheel_filename``.
114
- """
115
- _, _, _, tags = parse_wheel_filename(filename)
116
- return {str(tag) for tag in tags}
110
+ class InvalidWheelFilename(Exception):
111
+ """An invalid wheel filename was found, users should refer to PEP 427."""
117
112
 
118
- except ModuleNotFoundError:
119
- # fall back to a local implementation
120
- # that is heavily inspired by / almost vendored from the `packaging` package:
121
- def tags_from_wheel_filename(filename: str) -> Set[str]:
122
- """
123
- Parses a wheel filename into a list of compatible platform tags.
124
113
 
125
- Implemented as (semi-)vendored functionality in req2flatpak.
126
- """
127
- InvalidWheelFilename = Exception
128
- Tag = Tuple[str, str, str]
129
-
130
- # the following code is based on packaging.tags.parse_tag,
131
- # it is needed for the parse_wheel_filename function:
132
- def parse_tag(tag: str) -> FrozenSet[Tag]:
133
- tags: Set[Tag] = set()
134
- interpreters, abis, platforms = tag.split("-")
135
- for interpreter in interpreters.split("."):
136
- for abi in abis.split("."):
137
- for platform_ in platforms.split("."):
138
- tags.add((interpreter, abi, platform_))
139
- return frozenset(tags)
140
-
141
- # the following code is based on packaging.utils.parse_wheel_filename:
142
- # pylint: disable=redefined-outer-name
143
- def parse_wheel_filename(
144
- wheel_filename: str,
145
- ) -> Iterable[Tag]:
146
- if not wheel_filename.endswith(".whl"):
147
- raise InvalidWheelFilename(
148
- "Error parsing wheel filename: "
149
- "Invalid wheel filename (extension must be '.whl'): "
150
- f"{wheel_filename}"
151
- )
152
- wheel_filename = wheel_filename[:-4]
153
- dashes = wheel_filename.count("-")
154
- if dashes not in (4, 5):
155
- raise InvalidWheelFilename(
156
- "Error parsing wheel filename: "
157
- "Invalid wheel filename (wrong number of parts): "
158
- f"{wheel_filename}"
159
- )
160
- parts = wheel_filename.split("-", dashes - 2)
161
- 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.
162
117
 
163
- 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}
164
122
 
165
123
 
166
124
  # =============================================================================
@@ -227,6 +185,17 @@ class Download(Requirement):
227
185
  return "aarch64"
228
186
  return None
229
187
 
188
+ def __lt__(self, other):
189
+ """Makes this class sortable."""
190
+ # Note: Implementing __lt__ is sufficient to make a class sortable,
191
+ # see, e.g., https://stackoverflow.com/a/7152796
192
+
193
+ def sort_keys(download: Download) -> Tuple[str, str, str]:
194
+ """A tuple of package, version and architecture is used as key for sorting."""
195
+ return download.package, download.version, download.arch or ""
196
+
197
+ return sort_keys(self) < sort_keys(other)
198
+
230
199
 
231
200
  @dataclass(frozen=True)
232
201
  class Release(Requirement):
@@ -254,17 +223,8 @@ class PlatformFactory:
254
223
 
255
224
  @staticmethod
256
225
  def _get_current_python_tags() -> List[str]:
257
- try:
258
- # pylint: disable=import-outside-toplevel
259
- import packaging.tags
260
-
261
- tags = [str(tag) for tag in packaging.tags.sys_tags()]
262
- return tags
263
- except ModuleNotFoundError as e:
264
- logger.warning(
265
- 'Error trying to import the "packaging" package.', exc_info=e
266
- )
267
- return []
226
+ tags = [str(tag) for tag in packaging.tags.sys_tags()]
227
+ return tags
268
228
 
269
229
  @classmethod
270
230
  def from_current_interpreter(cls) -> Platform:
@@ -308,7 +268,7 @@ class PlatformFactory:
308
268
 
309
269
  @classmethod
310
270
  def from_python_version_and_arch(
311
- cls, minor_version: int = None, arch="x86_64"
271
+ cls, minor_version: Optional[int] = None, arch="x86_64"
312
272
  ) -> Platform:
313
273
  """
314
274
  Returns a platform object that roughly describes a cpython installation on linux.
@@ -333,7 +293,7 @@ class PlatformFactory:
333
293
 
334
294
  @classmethod
335
295
  def _cp3_linux_tags(
336
- cls, minor_version: int = None, arch="x86_64"
296
+ cls, minor_version: Optional[int] = None, arch="x86_64"
337
297
  ) -> Generator[str, None, None]:
338
298
  """Yields python platform tags for cpython3 on linux."""
339
299
  # pylint: disable=too-many-branches
@@ -414,27 +374,32 @@ class RequirementsParser:
414
374
  resolve dependencies.
415
375
  """
416
376
 
417
- # based on: https://stackoverflow.com/a/59971236
418
- # using functionality from pkg_resources.parse_requirements
419
-
420
377
  @classmethod
421
378
  def parse_string(cls, requirements_txt: str) -> List[Requirement]:
422
379
  """Parses requirements.txt string content into a list of Requirement objects."""
423
380
 
424
- def validate_requirement(req: pkg_resources.Requirement) -> None:
425
- assert (
426
- len(req.specs) == 1
427
- ), "Error parsing requirements: A single version number must be specified."
428
- assert (
429
- req.specs[0][0] == "=="
430
- ), "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
+ )
431
388
 
432
- def make_requirement(req: pkg_resources.Requirement) -> Requirement:
389
+ def make_requirement(req: packaging_reqs.Requirement) -> Requirement:
433
390
  validate_requirement(req)
434
- return Requirement(package=req.project_name, version=req.specs[0][1])
391
+ return Requirement(package=req.name, version=list(req.specifier)[0].version)
392
+
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))
435
401
 
436
- reqs = pkg_resources.parse_requirements(requirements_txt)
437
- return [make_requirement(req) for req in reqs]
402
+ return requirements
438
403
 
439
404
  @classmethod
440
405
  def parse_file(cls, file) -> List[Requirement]:
@@ -485,6 +450,8 @@ class PypiClient:
485
450
  url = f"https://pypi.org/pypi/{req.package}/{req.version}/json"
486
451
  json_string = cls._query(url)
487
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?")
488
455
  return Release(
489
456
  package=req.package,
490
457
  version=req.version,
@@ -537,9 +504,7 @@ class DownloadChooser:
537
504
  preferred downloads are returned first.
538
505
  """
539
506
  cache = set()
540
- for (platform_tag, download) in product(
541
- platform.python_tags, release.downloads
542
- ):
507
+ for platform_tag, download in product(platform.python_tags, release.downloads):
543
508
  if download in cache:
544
509
  continue
545
510
  if wheels_only and not download.is_wheel:
@@ -584,14 +549,18 @@ class DownloadChooser:
584
549
  class FlatpakGenerator:
585
550
  """Provides methods for generating a flatpak-builder build module."""
586
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
+
587
558
  @staticmethod
588
559
  def build_module(
589
560
  requirements: Iterable[Requirement],
590
561
  downloads: Iterable[Download],
591
562
  module_name="python3-package-installation",
592
- pip_install_template: str = "pip3 install --verbose --exists-action=i "
593
- '--no-index --find-links="file://${PWD}" '
594
- "--prefix=${FLATPAK_DEST} --no-build-isolation ",
563
+ pip_install_template: str = pip_install_template,
595
564
  ) -> dict:
596
565
  """Generates a build module for inclusion in a flatpak-builder build manifest."""
597
566
 
@@ -606,10 +575,7 @@ class FlatpakGenerator:
606
575
  return source
607
576
 
608
577
  def sources(downloads: Iterable[Download]) -> List[dict]:
609
- sorted_downloads = sorted(
610
- downloads, key=lambda d: (d.package, d.version, d.arch)
611
- )
612
- return [source(download) for download in sorted_downloads]
578
+ return [source(download) for download in sorted(downloads)]
613
579
 
614
580
  return {
615
581
  "name": module_name,
@@ -623,12 +589,30 @@ class FlatpakGenerator:
623
589
  @classmethod
624
590
  def build_module_as_str(cls, *args, **kwargs) -> str:
625
591
  """
626
- Generates a build module for inclusion in a flatpak-builder build manifest.
592
+ Generate JSON build module for inclusion in a flatpak-builder build manifest.
627
593
 
628
594
  The args and kwargs are the same as in
629
595
  :py:meth:`~req2flatpak.FlatpakGenerator.build_module`
630
596
  """
631
- return json.dumps(cls.build_module(*args, **kwargs), indent=2)
597
+ return json.dumps(cls.build_module(*args, **kwargs), indent=4)
598
+
599
+ @classmethod
600
+ def build_module_as_yaml_str(cls, *args, **kwargs) -> str:
601
+ """
602
+ Generate YAML build module for inclusion in a flatpak-builder build manifest.
603
+
604
+ The args and kwargs are the same as in
605
+ :py:meth:`~req2flatpak.FlatpakGenerator.build_module`
606
+ """
607
+ # optional dependency, not imported at top
608
+ if not yaml:
609
+ raise ImportError(
610
+ "Package `pyyaml` has to be installed for the yaml format."
611
+ )
612
+
613
+ return yaml.dump(
614
+ cls.build_module(*args, **kwargs), default_flow_style=False, sort_keys=False
615
+ )
632
616
 
633
617
 
634
618
  # =============================================================================
@@ -665,12 +649,22 @@ def cli_parser() -> argparse.ArgumentParser:
665
649
  default=False,
666
650
  help="Uses a persistent cache when querying pypi.",
667
651
  )
652
+ parser.add_argument(
653
+ "--yaml",
654
+ action="store_true",
655
+ help="Write YAML instead of the default JSON. Needs the 'pyyaml' package.",
656
+ )
657
+
668
658
  parser.add_argument(
669
659
  "--outfile",
670
660
  "-o",
671
661
  nargs="?",
672
662
  type=argparse.FileType("w"),
673
663
  default=sys.stdout,
664
+ help="""
665
+ By default, writes JSON but specify a '.yaml' extension and YAML
666
+ will be written instead, provided you have the 'pyyaml' package.
667
+ """,
674
668
  )
675
669
  parser.add_argument(
676
670
  "--platform-info",
@@ -687,7 +681,7 @@ def cli_parser() -> argparse.ArgumentParser:
687
681
  return parser
688
682
 
689
683
 
690
- def main():
684
+ def main(): # pylint: disable=too-many-branches
691
685
  """Main function that provides req2flatpak's commandline interface."""
692
686
 
693
687
  # process commandline arguments
@@ -695,19 +689,33 @@ def main():
695
689
  options = parser.parse_args()
696
690
 
697
691
  # stream output to a file or to stdout
698
- output_stream = options.outfile if hasattr(options.outfile, "write") else sys.stdout
692
+ if hasattr(options.outfile, "write"):
693
+ output_stream = options.outfile
694
+ if pathlib.Path(output_stream.name).suffix.casefold() in (".yaml", ".yml"):
695
+ options.yaml = True
696
+ else:
697
+ output_stream = sys.stdout
698
+
699
+ if options.yaml and not yaml:
700
+ parser.error(
701
+ "Outputing YAML requires 'pyyaml' package: try 'pip install pyyaml'"
702
+ )
699
703
 
700
704
  # print platform info if requested, and exit
701
705
  if options.platform_info:
702
- json.dump(
703
- asdict(PlatformFactory.from_current_interpreter()), output_stream, indent=4
704
- )
706
+ info = asdict(PlatformFactory.from_current_interpreter())
707
+ if options.yaml:
708
+ yaml.dump(info, output_stream, default_flow_style=False, sort_keys=False)
709
+ else:
710
+ json.dump(info, output_stream, indent=4)
705
711
  parser.exit()
706
712
 
707
713
  # print installed packages if requested, and exit
708
714
  if options.installed_packages:
709
715
  # pylint: disable=not-an-iterable
710
- 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
+ }
711
719
  for pkg, version in pkgs.items():
712
720
  print(f"{pkg}=={version}", file=output_stream)
713
721
  parser.exit()
@@ -755,13 +763,24 @@ def main():
755
763
  DownloadChooser.wheel_or_sdist(release, platform)
756
764
  for release in releases
757
765
  for platform in platforms
766
+ if platform
758
767
  }
759
768
 
760
769
  # generate flatpak-builder build module
761
- build_module = FlatpakGenerator.build_module(requirements, downloads)
762
-
763
- # write output
764
- json.dump(build_module, output_stream, indent=4)
770
+ if options.yaml:
771
+ try:
772
+ name = pathlib.Path(__file__).name
773
+ except NameError:
774
+ name = "req2flatpak"
775
+ output_stream.write(f"# Generated by {name} {' '.join(sys.argv[1:])}\n")
776
+ output_stream.write(
777
+ FlatpakGenerator.build_module_as_yaml_str(requirements, downloads)
778
+ )
779
+ else:
780
+ # write json
781
+ output_stream.write(
782
+ FlatpakGenerator.build_module_as_str(requirements, downloads)
783
+ )
765
784
 
766
785
 
767
786
  if __name__ == "__main__":
@@ -1,69 +0,0 @@
1
- [tool.poetry]
2
- name = "req2flatpak"
3
- version = "0.1.0"
4
- description = "Generates a flatpak-builder build module for installing python packages defined in requirements.txt files."
5
- authors = ["johannesjh <johannesjh@users.noreply.github.com>"]
6
- license = "MIT"
7
- readme = "README.rst"
8
-
9
- [tool.poetry.scripts]
10
- req2flatpak = 'req2flatpak:main'
11
-
12
- [tool.poetry.dependencies]
13
- python = "^3.7.2"
14
- packaging = { version = "^21.3", optional = true }
15
-
16
- [tool.poetry.group.lint.dependencies]
17
- pylama = { extras = [
18
- "mypy",
19
- "pylint",
20
- "eradicate",
21
- "toml",
22
- ], version = "^8.4.1" }
23
- bandit = { extras = ["toml"], version = "^1.7.4" }
24
- types-setuptools = "^65.5.0.2" # type stubs for mypy linting
25
-
26
- # [tool.poetry.group.tests.dependencies]
27
-
28
- [tool.poetry.group.docs]
29
- optional = true
30
-
31
- [tool.poetry.group.docs.dependencies]
32
- sphinx = "^5.3.0"
33
- sphinx-argparse = "^0.3.2"
34
- sphinx-rtd-theme-github-versions = "^1.1"
35
-
36
- [tool.poetry.extras]
37
- packaging = ["packaging"]
38
-
39
- [build-system]
40
- requires = ["poetry-core"]
41
- build-backend = "poetry.core.masonry.api"
42
-
43
- [tool.isort]
44
- profile = "black"
45
- skip_gitignore = true
46
-
47
- [tool.pylama]
48
- max_line_length = 88
49
- concurrent = true
50
- linters = "pycodestyle,pydocstyle,pyflakes,pylint,eradicate,mypy"
51
-
52
- [tool.pylama.linter.pycodestyle]
53
- ignore = "W503,E203,E501"
54
-
55
- # [tool.pycodestyle] -> see setup.cfg
56
-
57
- [tool.pylama.linter.pydocstyle]
58
- ignore = "D202,D203,D205,D401,D212"
59
-
60
- [tool.pydocstyle]
61
- ignore = ["D202", "D203", "D205", "D401", "D212"]
62
-
63
- [tool.pylint]
64
- format.max-line-length = 88
65
- main.disable = ["C0301"] # ignore line-too-long
66
- basic.good-names = ["e", "f", "py"]
67
-
68
- [tool.bandit]
69
- exclude_dirs = ['tests']
@@ -1,29 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- modules = \
5
- ['req2flatpak']
6
- extras_require = \
7
- {'packaging': ['packaging>=21.3,<22.0']}
8
-
9
- entry_points = \
10
- {'console_scripts': ['req2flatpak = req2flatpak:main']}
11
-
12
- setup_kwargs = {
13
- 'name': 'req2flatpak',
14
- 'version': '0.1.0',
15
- 'description': 'Generates a flatpak-builder build module for installing python packages defined in requirements.txt files.',
16
- 'long_description': "req2flatpak\n===========\n\n.. inclusion-marker-do-not-remove\n\n``req2flatpak`` is a script to convert python package requirements\nto a flatpak-builder build module.\nThe module will install the required python packages\nas part of a flatpak build.\n\n\nIntended Use\n------------\n\nreq2flatpak is for programmers\nwho want to package a python application using flatpak.\n\nThe req2flatpak script takes python package requirements as input, e.g., as\n``requirements.txt`` file. It allows to specify the target platform’s\npython version and architecture. The script outputs an automatically\ngenerated ``flatpak-builder`` build module. The build module, if included\ninto a flatpak-builder build manifest, will install the python packages\nusing pip.\n\nGetting Started\n---------------\n\nRun ``pip install git+https://github.com/johannesjh/req2flatpak``\nto install the latest development version of req2flatpak.\n\nIt is possible to use req2flatpak from the commandline,\nas well as programmatically from a python script.\n\nCommandline usage means you can invoke req2flatpak’s commandline interface\nas follows, in order to generate a ``flatpak-builder`` build module\nfrom given python package requirements:\n\n.. code:: bash\n\n ./req2flatpak.py --requirements-file requirements.txt --target-platforms 310-x86_64 310-aarch64\n\nWhen invoked like this, req2flatpak will\nread the requirements file,\nquery pypi about available downloads for the requirements,\nchoose appropriate downloads for the specified target platforms,\nand generate a flatpak-builder build module.\nThe module, if included in a flatpak-builder build manifest,\nwill install the required packages using pip.\n\nThe commandline option to define target platforms uses the format ``<pythonversion>-<architecture>``.\nTo learn more about available commandline options,\nrun ``req2flatpak.py --help``.\n\nProgrammatic usage is also possible.\nThis means you can invoke functionality from req2flatpak in your own python script,\nallowing you to tweak the desired behavior in many ways.\nThe `documentation <https://johannesjh.github.io/req2flatpak/>`__\ndescribes req2flatpak's python api and includes code examples\nto help you get started quickly.\n\n\nDocumentation\n-------------\n\nSee https://johannesjh.github.io/req2flatpak/\n\n\nContributing\n------------\n\nreq2flatpak is developed in an open-source, community-driven way, as a\nvoluntary effort in the authors’ free time.\n\nAll contributions are greatly appreciated… pull requests are welcome,\nand so are bug reports and suggestions for improvement.\nSee req2flatpak’s documentation for how to set up a development environment\nand how to contribute back to req2flatpak.\n\nRelated Work\n------------\n\nThe\n`flatpak-pip-generator <https://github.com/flatpak/flatpak-builder-tools/blob/master/pip/flatpak-pip-generator>`__\nscript is very similar to this project. Both scripts basically serve the same purpose,\nand this project took a lot of inspiration from\nflatpak-pip-generator. In fact, this project was created when we\ndiscussed feature request\n`#296 <https://github.com/flatpak/flatpak-builder-tools/issues/296>`__\nin flatpak-pip-generator. A prototype followed from this feature\nrequest, and since it was written from scratch, the prototype became\nthis separate project.\n\nComparison between ``flatpak-pip-generator`` and ``req2flatpak.py``:\nEach of the two projects has its own benefits.\nA comparison will likely change over time.\nAs in Oct, 2022, in my personal opinion (johannesjh),\nI see the following similarities and differences:\n\n- Both projects generate build modules for flatpak-builder.\n- Both projects consist of a single script file with minimal\n dependencies, and are thus very easy to install.\n- ``flatpak-pip-generator`` resolves dependencies and freezes\n dependency versions, whereas ``req2flatpak.py`` asks the user to\n provide a fully resolved list of dependencies with frozen dependency\n versions. Various tools exist which make this easy, e.g.,\n pip, pip-compile and poetry.\n- ``flatpak-pip-generator`` is older and thus likely to be more mature.\n It supports more commandline options and probably has a more complete\n feature set.\n- ``req2flatpak.py`` is faster. The script itself runs faster because\n it does not need to download package files in order to generate the\n build module. And the flatpak build runs faster because all packages\n (from the entire ``requirements.txt`` file) are installed in a single\n call to ``pip install``.\n- ``req2flatpak.py`` re-implements some functionality of pip. In\n contrast, ``flatpak-pip-generator`` uses pip’s official\n functionality. Specifically, ``req2flatpak.py`` re-implements how pip\n queries available downloads from pypi and how pip chooses suitable\n downloads to match a given target platform.\n- ``req2flatpak.py`` prefers binary wheels, whereas\n ``flatpak-pip-generator`` prefers source packages.\n\nLicense\n-------\n\nreq2flatpak is MIT-licensed, see the ``COPYING`` file.\n",
17
- 'author': 'johannesjh',
18
- 'author_email': 'johannesjh@users.noreply.github.com',
19
- 'maintainer': 'None',
20
- 'maintainer_email': 'None',
21
- 'url': 'None',
22
- 'py_modules': modules,
23
- 'extras_require': extras_require,
24
- 'entry_points': entry_points,
25
- 'python_requires': '>=3.7.2,<4.0.0',
26
- }
27
-
28
-
29
- setup(**setup_kwargs)