requirements-detector 1.3.1__tar.gz → 1.4.0__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.
Files changed (24) hide show
  1. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/PKG-INFO +7 -7
  2. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/README.md +1 -1
  3. requirements_detector-1.4.0/pyproject.toml +52 -0
  4. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/__init__.py +11 -0
  5. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/detect.py +48 -15
  6. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/poetry_semver/__init__.py +14 -7
  7. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/poetry_semver/patterns.py +6 -2
  8. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/poetry_semver/version.py +49 -41
  9. requirements_detector-1.4.0/requirements_detector/poetry_semver/version_constraint.py +27 -0
  10. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/poetry_semver/version_range.py +46 -25
  11. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/poetry_semver/version_union.py +16 -10
  12. requirements_detector-1.4.0/requirements_detector/py.typed +0 -0
  13. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/requirement.py +23 -6
  14. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/run.py +0 -1
  15. requirements_detector-1.3.1/pyproject.toml +0 -49
  16. requirements_detector-1.3.1/requirements_detector/poetry_semver/version_constraint.py +0 -27
  17. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/LICENSE +0 -0
  18. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/__main__.py +0 -0
  19. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/exceptions.py +0 -0
  20. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/formatters.py +0 -0
  21. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/handle_setup.py +0 -0
  22. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/poetry_semver/README.md +0 -0
  23. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/poetry_semver/empty_constraint.py +0 -0
  24. {requirements_detector-1.3.1 → requirements_detector-1.4.0}/requirements_detector/poetry_semver/exceptions.py +0 -0
@@ -1,29 +1,29 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: requirements-detector
3
- Version: 1.3.1
3
+ Version: 1.4.0
4
4
  Summary: Python tool to find and list requirements of a Python project
5
- Home-page: https://github.com/landscapeio/requirements-detector
6
5
  License: MIT
7
6
  Keywords: python,requirements detector
8
7
  Author: Landscape.io
9
8
  Author-email: code@landscape.io
10
- Requires-Python: >=3.8,<4.0
9
+ Requires-Python: >=3.9,<4.0
11
10
  Classifier: Development Status :: 5 - Production/Stable
12
11
  Classifier: Environment :: Console
13
12
  Classifier: Intended Audience :: Developers
14
13
  Classifier: License :: OSI Approved :: MIT License
15
14
  Classifier: Operating System :: Unix
16
15
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.8
18
16
  Classifier: Programming Language :: Python :: 3.9
19
17
  Classifier: Programming Language :: Python :: 3.10
20
18
  Classifier: Programming Language :: Python :: 3.11
21
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
22
21
  Classifier: Topic :: Software Development :: Quality Assurance
23
22
  Requires-Dist: astroid (>=3.0,<4.0)
24
23
  Requires-Dist: packaging (>=21.3)
25
24
  Requires-Dist: semver (>=3.0.0,<4.0.0)
26
- Requires-Dist: toml (>=0.10.2,<0.11.0)
25
+ Requires-Dist: tomli (>=2.2.1,<3.0.0) ; python_version < "3.11"
26
+ Project-URL: Homepage, https://github.com/landscapeio/requirements-detector
27
27
  Description-Content-Type: text/markdown
28
28
 
29
29
  # Requirements Detector
@@ -45,7 +45,7 @@ When run from the root of a Python project, it will try to ascertain which libra
45
45
  It uses the following methods in order, in the root of the project:
46
46
 
47
47
  1. Parse `setup.py` (if this is successful, the remaining steps are skipped)
48
- 2. Parse `pyproject.yoml` (if a `tool.poetry.dependencies` section is found, the remaining steps are skipped)
48
+ 2. Parse `pyproject.toml` (if a `tool.poetry.dependencies` section is found, the remaining steps are skipped)
49
49
  3. Parse `requirements.txt` or `requirements.pip`
50
50
  4. Parse all `*.txt` and `*.pip` files inside a folder called `requirements`
51
51
  5. Parse all files in the root folder matching `*requirements*.txt` or `reqs.txt` (so for example, `pip_requirements.txt` would match, as would `requirements_common.txt`)
@@ -17,7 +17,7 @@ When run from the root of a Python project, it will try to ascertain which libra
17
17
  It uses the following methods in order, in the root of the project:
18
18
 
19
19
  1. Parse `setup.py` (if this is successful, the remaining steps are skipped)
20
- 2. Parse `pyproject.yoml` (if a `tool.poetry.dependencies` section is found, the remaining steps are skipped)
20
+ 2. Parse `pyproject.toml` (if a `tool.poetry.dependencies` section is found, the remaining steps are skipped)
21
21
  3. Parse `requirements.txt` or `requirements.pip`
22
22
  4. Parse all `*.txt` and `*.pip` files inside a folder called `requirements`
23
23
  5. Parse all files in the root folder matching `*requirements*.txt` or `reqs.txt` (so for example, `pip_requirements.txt` would match, as would `requirements_common.txt`)
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ build-backend = "poetry.core.masonry.api"
3
+ requires = [ "poetry-core>=1" ]
4
+
5
+ [tool.poetry]
6
+ name = "requirements-detector"
7
+ version = "1.4.0"
8
+ authors = [ "Landscape.io <code@landscape.io>" ]
9
+ classifiers = [
10
+ 'Development Status :: 5 - Production/Stable',
11
+ 'Environment :: Console',
12
+ 'Intended Audience :: Developers',
13
+ 'Operating System :: Unix',
14
+ 'Topic :: Software Development :: Quality Assurance',
15
+ 'Programming Language :: Python :: 3.9',
16
+ 'Programming Language :: Python :: 3.10',
17
+ 'Programming Language :: Python :: 3.11',
18
+ 'Programming Language :: Python :: 3.12',
19
+ 'Programming Language :: Python :: 3.13',
20
+ 'License :: OSI Approved :: MIT License',
21
+ ]
22
+ license = "MIT"
23
+ keywords = [ "python", "requirements detector" ]
24
+ description = "Python tool to find and list requirements of a Python project"
25
+ readme = "README.md"
26
+ homepage = "https://github.com/landscapeio/requirements-detector"
27
+ packages = [
28
+ { include = "requirements_detector/" },
29
+ ]
30
+ include = [
31
+ "c2cgeoform/py.typed",
32
+ ]
33
+
34
+ [tool.poetry.scripts]
35
+ detect-requirements = 'requirements_detector.run:run'
36
+
37
+ [tool.poetry.dependencies]
38
+ python = ">=3.9,<4.0"
39
+ astroid = "^3.0"
40
+ packaging = ">=21.3"
41
+ tomli = { version = "^2.2.1", python = "<3.11" }
42
+ semver = "^3.0.0"
43
+
44
+ [tool.poetry.dev-dependencies]
45
+ tox = "^3.24.5"
46
+ pre-commit = "^4.2.0"
47
+ pytest = "^6.2.4"
48
+ twine = "^6.1.0"
49
+ coverage = "^5.5"
50
+ pytest-benchmark = "^3.4.1"
51
+ pytest-cov = "^2.12.1"
52
+ types-toml = "^0.10.4"
@@ -8,3 +8,14 @@ from requirements_detector.detect import ( # from_setup_py,
8
8
  from_requirements_txt,
9
9
  from_setup_py,
10
10
  )
11
+
12
+ __all__ = [
13
+ "CouldNotParseRequirements",
14
+ "RequirementsNotFound",
15
+ "find_requirements",
16
+ "from_pyproject_toml",
17
+ "from_requirements_blob",
18
+ "from_requirements_dir",
19
+ "from_requirements_txt",
20
+ "from_setup_py",
21
+ ]
@@ -1,14 +1,21 @@
1
1
  import re
2
+ import sys
2
3
  from pathlib import Path
3
- from typing import List, Union
4
-
5
- import toml
4
+ from typing import List, Optional, Union
6
5
 
7
6
  from .exceptions import CouldNotParseRequirements, RequirementsNotFound
8
7
  from .handle_setup import from_setup_py
9
8
  from .poetry_semver import parse_constraint
9
+ from .poetry_semver.version_constraint import VersionConstraint
10
10
  from .requirement import DetectedRequirement
11
11
 
12
+ try:
13
+ # added in Python 3.11: https://docs.python.org/3/library/tomllib.html
14
+ import tomllib
15
+ except ImportError:
16
+ # for Python <= 3.10:
17
+ import tomli as tomllib
18
+
12
19
  __all__ = [
13
20
  "find_requirements",
14
21
  "from_requirements_txt",
@@ -81,7 +88,7 @@ def find_requirements(path: P) -> List[DetectedRequirement]:
81
88
  if reqfile.exists and reqfile.is_file():
82
89
  try:
83
90
  requirements += from_requirements_txt(reqfile)
84
- except CouldNotParseRequirements as e:
91
+ except CouldNotParseRequirements:
85
92
  pass
86
93
 
87
94
  requirements_dir = path / "requirements"
@@ -102,13 +109,34 @@ def find_requirements(path: P) -> List[DetectedRequirement]:
102
109
  raise RequirementsNotFound
103
110
 
104
111
 
112
+ def _version_from_spec(spec: Union[list, dict, str]) -> Optional[VersionConstraint]:
113
+ if isinstance(spec, list):
114
+ constraint = None
115
+ for new_constraint in [_version_from_spec(s) for s in spec]:
116
+ if constraint is None:
117
+ constraint = new_constraint
118
+ elif new_constraint is not None:
119
+ constraint = constraint.union(new_constraint)
120
+ return constraint
121
+
122
+ if isinstance(spec, dict):
123
+ if "version" in spec:
124
+ spec = spec["version"]
125
+ else:
126
+ return None
127
+
128
+ return parse_constraint(spec)
129
+
130
+
105
131
  def from_pyproject_toml(toml_file: P) -> List[DetectedRequirement]:
106
132
  requirements = []
107
133
 
108
134
  if isinstance(toml_file, str):
109
135
  toml_file = Path(toml_file)
110
136
 
111
- parsed = toml.load(toml_file)
137
+ with open(toml_file, "rb") as toml_file_open:
138
+ parsed = tomllib.load(toml_file_open)
139
+
112
140
  poetry_section = parsed.get("tool", {}).get("poetry", {})
113
141
  dependencies = poetry_section.get("dependencies", {})
114
142
  dependencies.update(poetry_section.get("dev-dependencies", {}))
@@ -116,16 +144,21 @@ def from_pyproject_toml(toml_file: P) -> List[DetectedRequirement]:
116
144
  for name, spec in dependencies.items():
117
145
  if name.lower() == "python":
118
146
  continue
119
- if isinstance(spec, dict):
120
- if "version" in spec:
121
- spec = spec["version"]
122
- else:
123
- req = DetectedRequirement.parse(f"{name}", toml_file)
124
- if req is not None:
125
- requirements.append(req)
126
- continue
127
- parsed_spec = str(parse_constraint(spec))
128
- if "," not in parsed_spec and "<" not in parsed_spec and ">" not in parsed_spec and "=" not in parsed_spec:
147
+
148
+ parsed_spec_obj = _version_from_spec(spec)
149
+ if parsed_spec_obj is None and isinstance(spec, dict) and "version" not in spec:
150
+ req = DetectedRequirement.parse(f"{name}", toml_file)
151
+ if req is not None:
152
+ requirements.append(req)
153
+ continue
154
+ assert parsed_spec_obj is not None
155
+ parsed_spec = str(parsed_spec_obj)
156
+ if (
157
+ "," not in parsed_spec
158
+ and "<" not in parsed_spec
159
+ and ">" not in parsed_spec
160
+ and "=" not in parsed_spec
161
+ ):
129
162
  parsed_spec = f"=={parsed_spec}"
130
163
 
131
164
  req = DetectedRequirement.parse(f"{name}{parsed_spec}", toml_file)
@@ -1,6 +1,5 @@
1
1
  import re
2
2
 
3
- from .empty_constraint import EmptyConstraint
4
3
  from .patterns import (
5
4
  BASIC_CONSTRAINT,
6
5
  CARET_CONSTRAINT,
@@ -16,14 +15,16 @@ from .version_union import VersionUnion
16
15
  __version__ = "0.1.0"
17
16
 
18
17
 
19
- def parse_constraint(constraints): # type: (str) -> VersionConstraint
18
+ def parse_constraint(constraints: str) -> VersionConstraint:
20
19
  if constraints == "*":
21
20
  return VersionRange()
22
21
 
23
22
  or_constraints = re.split(r"\s*\|\|?\s*", constraints.strip())
24
23
  or_groups = []
25
24
  for constraints in or_constraints:
26
- and_constraints = re.split("(?<!^)(?<![=>< ,]) *(?<!-)[, ](?!-) *(?!,|$)", constraints)
25
+ and_constraints = re.split(
26
+ "(?<!^)(?<![=>< ,]) *(?<!-)[, ](?!-) *(?!,|$)", constraints
27
+ )
27
28
  constraint_objects = []
28
29
 
29
30
  if len(and_constraints) > 1:
@@ -47,7 +48,7 @@ def parse_constraint(constraints): # type: (str) -> VersionConstraint
47
48
  return VersionUnion.of(*or_groups)
48
49
 
49
50
 
50
- def parse_single_constraint(constraint): # type: (str) -> VersionConstraint
51
+ def parse_single_constraint(constraint: str) -> VersionConstraint:
51
52
  m = re.match(r"(?i)^v?[xX*](\.[xX*])*$", constraint)
52
53
  if m:
53
54
  return VersionRange()
@@ -61,7 +62,9 @@ def parse_single_constraint(constraint): # type: (str) -> VersionConstraint
61
62
  if len(m.group(1).split(".")) == 1:
62
63
  high = version.stable.next_major
63
64
 
64
- return VersionRange(version, high, include_min=True, always_include_max_prerelease=True)
65
+ return VersionRange(
66
+ version, high, include_min=True, always_include_max_prerelease=True
67
+ )
65
68
 
66
69
  # PEP 440 Tilde range (~=)
67
70
  m = TILDE_PEP440_CONSTRAINT.match(constraint)
@@ -82,7 +85,9 @@ def parse_single_constraint(constraint): # type: (str) -> VersionConstraint
82
85
  low = Version(version.major, version.minor, version.patch)
83
86
  high = version.stable.next_minor
84
87
 
85
- return VersionRange(low, high, include_min=True, always_include_max_prerelease=True)
88
+ return VersionRange(
89
+ low, high, include_min=True, always_include_max_prerelease=True
90
+ )
86
91
 
87
92
  # Caret range
88
93
  m = CARET_CONSTRAINT.match(constraint)
@@ -142,7 +147,9 @@ def parse_single_constraint(constraint): # type: (str) -> VersionConstraint
142
147
  try:
143
148
  version = Version.parse(version)
144
149
  except ValueError:
145
- raise ValueError("Could not parse version constraint: {}".format(constraint))
150
+ raise ValueError(
151
+ "Could not parse version constraint: {}".format(constraint)
152
+ )
146
153
 
147
154
  if op == "<":
148
155
  return VersionRange(max=version)
@@ -6,7 +6,9 @@ MODIFIERS = (
6
6
  r"([+-]?([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?"
7
7
  )
8
8
 
9
- _COMPLETE_VERSION = r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?{}(?:\+[^\s]+)?".format(MODIFIERS)
9
+ _COMPLETE_VERSION = (
10
+ r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?{}(?:\+[^\s]+)?".format(MODIFIERS)
11
+ )
10
12
 
11
13
  COMPLETE_VERSION = re.compile("(?i)" + _COMPLETE_VERSION)
12
14
 
@@ -14,4 +16,6 @@ CARET_CONSTRAINT = re.compile(r"(?i)^\^({})$".format(_COMPLETE_VERSION))
14
16
  TILDE_CONSTRAINT = re.compile("(?i)^~(?!=)({})$".format(_COMPLETE_VERSION))
15
17
  TILDE_PEP440_CONSTRAINT = re.compile("(?i)^~=({})$".format(_COMPLETE_VERSION))
16
18
  X_CONSTRAINT = re.compile(r"^(!=|==)?\s*v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.[xX*])+$")
17
- BASIC_CONSTRAINT = re.compile(r"(?i)^(<>|!=|>=?|<=?|==?)?\s*({}|dev)".format(_COMPLETE_VERSION))
19
+ BASIC_CONSTRAINT = re.compile(
20
+ r"(?i)^(<>|!=|>=?|<=?|==?)?\s*({}|dev)".format(_COMPLETE_VERSION)
21
+ )
@@ -16,15 +16,15 @@ class Version(VersionRange):
16
16
 
17
17
  def __init__(
18
18
  self,
19
- major, # type: int
20
- minor=None, # type: Optional[int]
21
- patch=None, # type: Optional[int]
22
- rest=None, # type: Optional[int]
23
- pre=None, # type: Optional[str]
24
- build=None, # type: Optional[str]
25
- text=None, # type: Optional[str]
26
- precision=None, # type: Optional[int]
27
- ): # type: (...) -> None
19
+ major: int,
20
+ minor: Optional[int] = None,
21
+ patch: Optional[int] = None,
22
+ rest: Optional[int] = None,
23
+ pre: Optional[str] = None,
24
+ build: Optional[str] = None,
25
+ text: Optional[str] = None,
26
+ precision: Optional[int] = None,
27
+ ) -> None:
28
28
  self._major = int(major)
29
29
  self._precision = None
30
30
  if precision is None:
@@ -92,27 +92,27 @@ class Version(VersionRange):
92
92
  self._build = self._split_parts(build)
93
93
 
94
94
  @property
95
- def major(self): # type: () -> int
95
+ def major(self) -> int:
96
96
  return self._major
97
97
 
98
98
  @property
99
- def minor(self): # type: () -> int
99
+ def minor(self) -> int:
100
100
  return self._minor
101
101
 
102
102
  @property
103
- def patch(self): # type: () -> int
103
+ def patch(self) -> int:
104
104
  return self._patch
105
105
 
106
106
  @property
107
- def rest(self): # type: () -> int
107
+ def rest(self) -> int:
108
108
  return self._rest
109
109
 
110
110
  @property
111
- def prerelease(self): # type: () -> List[str]
111
+ def prerelease(self) -> List[str]:
112
112
  return self._prerelease
113
113
 
114
114
  @property
115
- def build(self): # type: () -> List[str]
115
+ def build(self) -> List[str]:
116
116
  return self._build
117
117
 
118
118
  @property
@@ -120,7 +120,7 @@ class Version(VersionRange):
120
120
  return self._text
121
121
 
122
122
  @property
123
- def precision(self): # type: () -> int
123
+ def precision(self) -> int:
124
124
  return self._precision
125
125
 
126
126
  @property
@@ -131,28 +131,28 @@ class Version(VersionRange):
131
131
  return self.next_patch
132
132
 
133
133
  @property
134
- def next_major(self): # type: () -> Version
134
+ def next_major(self) -> "Version":
135
135
  if self.is_prerelease() and self.minor == 0 and self.patch == 0:
136
136
  return Version(self.major, self.minor, self.patch)
137
137
 
138
138
  return self._increment_major()
139
139
 
140
140
  @property
141
- def next_minor(self): # type: () -> Version
141
+ def next_minor(self) -> "Version":
142
142
  if self.is_prerelease() and self.patch == 0:
143
143
  return Version(self.major, self.minor, self.patch)
144
144
 
145
145
  return self._increment_minor()
146
146
 
147
147
  @property
148
- def next_patch(self): # type: () -> Version
148
+ def next_patch(self) -> "Version":
149
149
  if self.is_prerelease():
150
150
  return Version(self.major, self.minor, self.patch)
151
151
 
152
152
  return self._increment_patch()
153
153
 
154
154
  @property
155
- def next_breaking(self): # type: () -> Version
155
+ def next_breaking(self) -> "Version":
156
156
  if self.major == 0:
157
157
  if self.minor != 0:
158
158
  return self._increment_minor()
@@ -167,8 +167,10 @@ class Version(VersionRange):
167
167
  return self._increment_major()
168
168
 
169
169
  @property
170
- def first_prerelease(self): # type: () -> Version
171
- return Version.parse("{}.{}.{}-alpha.0".format(self.major, self.minor, self.patch))
170
+ def first_prerelease(self) -> "Version":
171
+ return Version.parse(
172
+ "{}.{}.{}-alpha.0".format(self.major, self.minor, self.patch)
173
+ )
172
174
 
173
175
  @property
174
176
  def min(self):
@@ -191,7 +193,7 @@ class Version(VersionRange):
191
193
  return True
192
194
 
193
195
  @classmethod
194
- def parse(cls, text): # type: (str) -> Version
196
+ def parse(cls, text: str) -> "Version":
195
197
  try:
196
198
  match = COMPLETE_VERSION.match(text)
197
199
  except TypeError:
@@ -221,25 +223,25 @@ class Version(VersionRange):
221
223
  def is_empty(self):
222
224
  return False
223
225
 
224
- def is_prerelease(self): # type: () -> bool
226
+ def is_prerelease(self) -> bool:
225
227
  return len(self._prerelease) > 0
226
228
 
227
- def allows(self, version): # type: (Version) -> bool
229
+ def allows(self, version: "Version") -> bool:
228
230
  return self == version
229
231
 
230
- def allows_all(self, other): # type: (VersionConstraint) -> bool
232
+ def allows_all(self, other: VersionConstraint) -> bool:
231
233
  return other.is_empty() or other == self
232
234
 
233
- def allows_any(self, other): # type: (VersionConstraint) -> bool
235
+ def allows_any(self, other: VersionConstraint) -> bool:
234
236
  return other.allows(self)
235
237
 
236
- def intersect(self, other): # type: (VersionConstraint) -> VersionConstraint
238
+ def intersect(self, other: VersionConstraint) -> VersionConstraint:
237
239
  if other.allows(self):
238
240
  return self
239
241
 
240
242
  return EmptyConstraint()
241
243
 
242
- def union(self, other): # type: (VersionConstraint) -> VersionConstraint
244
+ def union(self, other: VersionConstraint) -> VersionConstraint:
243
245
  from .version_range import VersionRange
244
246
 
245
247
  if other.allows(self):
@@ -264,25 +266,31 @@ class Version(VersionRange):
264
266
 
265
267
  return VersionUnion.of(self, other)
266
268
 
267
- def difference(self, other): # type: (VersionConstraint) -> VersionConstraint
269
+ def difference(self, other: VersionConstraint) -> VersionConstraint:
268
270
  if other.allows(self):
269
271
  return EmptyConstraint()
270
272
 
271
273
  return self
272
274
 
273
- def equals_without_prerelease(self, other): # type: (Version) -> bool
274
- return self.major == other.major and self.minor == other.minor and self.patch == other.patch
275
+ def equals_without_prerelease(self, other: "Version") -> bool:
276
+ return (
277
+ self.major == other.major
278
+ and self.minor == other.minor
279
+ and self.patch == other.patch
280
+ )
275
281
 
276
- def _increment_major(self): # type: () -> Version
282
+ def _increment_major(self) -> "Version":
277
283
  return Version(self.major + 1, 0, 0, precision=self._precision)
278
284
 
279
- def _increment_minor(self): # type: () -> Version
285
+ def _increment_minor(self) -> "Version":
280
286
  return Version(self.major, self.minor + 1, 0, precision=self._precision)
281
287
 
282
- def _increment_patch(self): # type: () -> Version
283
- return Version(self.major, self.minor, self.patch + 1, precision=self._precision)
288
+ def _increment_patch(self) -> "Version":
289
+ return Version(
290
+ self.major, self.minor, self.patch + 1, precision=self._precision
291
+ )
284
292
 
285
- def _normalize_prerelease(self, pre): # type: (str) -> str
293
+ def _normalize_prerelease(self, pre: str) -> str:
286
294
  if not pre:
287
295
  return
288
296
 
@@ -307,7 +315,7 @@ class Version(VersionRange):
307
315
 
308
316
  return "{}.{}".format(modifier, number)
309
317
 
310
- def _normalize_build(self, build): # type: (str) -> str
318
+ def _normalize_build(self, build: str) -> str:
311
319
  if not build:
312
320
  return
313
321
 
@@ -319,7 +327,7 @@ class Version(VersionRange):
319
327
 
320
328
  return build
321
329
 
322
- def _split_parts(self, text): # type: (str) -> List[Union[str, int]]
330
+ def _split_parts(self, text: str) -> List[Union[str, int]]:
323
331
  parts = text.split(".")
324
332
 
325
333
  for i, part in enumerate(parts):
@@ -389,7 +397,7 @@ class Version(VersionRange):
389
397
 
390
398
  return 0
391
399
 
392
- def _cmp_lists(self, a, b): # type: (List, List) -> int
400
+ def _cmp_lists(self, a: List, b: List) -> int:
393
401
  for i in range(max(len(a), len(b))):
394
402
  a_part = None
395
403
  if i < len(a):
@@ -422,7 +430,7 @@ class Version(VersionRange):
422
430
 
423
431
  return 0
424
432
 
425
- def __eq__(self, other): # type: (Version) -> bool
433
+ def __eq__(self, other: "Version") -> bool:
426
434
  if not isinstance(other, Version):
427
435
  return NotImplemented
428
436
 
@@ -0,0 +1,27 @@
1
+ import semver
2
+
3
+
4
+ class VersionConstraint:
5
+ def is_empty(self) -> bool:
6
+ raise NotImplementedError()
7
+
8
+ def is_any(self) -> bool:
9
+ raise NotImplementedError()
10
+
11
+ def allows(self, version: semver.Version) -> bool:
12
+ raise NotImplementedError()
13
+
14
+ def allows_all(self, other: "VersionConstraint") -> bool:
15
+ raise NotImplementedError()
16
+
17
+ def allows_any(self, other: "VersionConstraint") -> bool:
18
+ raise NotImplementedError()
19
+
20
+ def intersect(self, other: "VersionConstraint") -> "VersionConstraint":
21
+ raise NotImplementedError()
22
+
23
+ def union(self, other: "VersionConstraint") -> "VersionConstraint":
24
+ raise NotImplementedError()
25
+
26
+ def difference(self, other: "VersionConstraint") -> "VersionConstraint":
27
+ raise NotImplementedError()
@@ -22,7 +22,11 @@ class VersionRange(VersionConstraint):
22
22
  and not include_max
23
23
  and not full_max.is_prerelease()
24
24
  and not full_max.build
25
- and (min is None or not min.is_prerelease() or not min.equals_without_prerelease(full_max))
25
+ and (
26
+ min is None
27
+ or not min.is_prerelease()
28
+ or not min.equals_without_prerelease(full_max)
29
+ )
26
30
  ):
27
31
  full_max = full_max.first_prerelease
28
32
 
@@ -58,7 +62,7 @@ class VersionRange(VersionConstraint):
58
62
  def is_any(self):
59
63
  return self._min is None and self._max is None
60
64
 
61
- def allows(self, other): # type: (semver.Version) -> bool
65
+ def allows(self, other: semver.Version) -> bool:
62
66
  if self._min is not None:
63
67
  if other < self._min:
64
68
  return False
@@ -75,7 +79,7 @@ class VersionRange(VersionConstraint):
75
79
 
76
80
  return True
77
81
 
78
- def allows_all(self, other): # type: (VersionConstraint) -> bool
82
+ def allows_all(self, other: VersionConstraint) -> bool:
79
83
  from .version import Version
80
84
 
81
85
  if other.is_empty():
@@ -92,7 +96,7 @@ class VersionRange(VersionConstraint):
92
96
 
93
97
  raise ValueError("Unknown VersionConstraint type {}.".format(other))
94
98
 
95
- def allows_any(self, other): # type: (VersionConstraint) -> bool
99
+ def allows_any(self, other: VersionConstraint) -> bool:
96
100
  from .version import Version
97
101
 
98
102
  if other.is_empty():
@@ -105,11 +109,13 @@ class VersionRange(VersionConstraint):
105
109
  return any([self.allows_any(constraint) for constraint in other.ranges])
106
110
 
107
111
  if isinstance(other, VersionRange):
108
- return not other.is_strictly_lower(self) and not other.is_strictly_higher(self)
112
+ return not other.is_strictly_lower(self) and not other.is_strictly_higher(
113
+ self
114
+ )
109
115
 
110
116
  raise ValueError("Unknown VersionConstraint type {}.".format(other))
111
117
 
112
- def intersect(self, other): # type: (VersionConstraint) -> VersionConstraint
118
+ def intersect(self, other: VersionConstraint) -> VersionConstraint:
113
119
  from .version import Version
114
120
 
115
121
  if other.is_empty():
@@ -160,9 +166,11 @@ class VersionRange(VersionConstraint):
160
166
  return intersect_min
161
167
 
162
168
  # If we got here, there is an actual range.
163
- return VersionRange(intersect_min, intersect_max, intersect_include_min, intersect_include_max)
169
+ return VersionRange(
170
+ intersect_min, intersect_max, intersect_include_min, intersect_include_max
171
+ )
164
172
 
165
- def union(self, other): # type: (VersionConstraint) -> VersionConstraint
173
+ def union(self, other: VersionConstraint) -> VersionConstraint:
166
174
  from .version import Version
167
175
 
168
176
  if isinstance(other, Version):
@@ -170,19 +178,23 @@ class VersionRange(VersionConstraint):
170
178
  return self
171
179
 
172
180
  if other == self.min:
173
- return VersionRange(self.min, self.max, include_min=True, include_max=self.include_max)
181
+ return VersionRange(
182
+ self.min, self.max, include_min=True, include_max=self.include_max
183
+ )
174
184
 
175
185
  if other == self.max:
176
- return VersionRange(self.min, self.max, include_min=self.include_min, include_max=True)
186
+ return VersionRange(
187
+ self.min, self.max, include_min=self.include_min, include_max=True
188
+ )
177
189
 
178
190
  return VersionUnion.of(self, other)
179
191
 
180
192
  if isinstance(other, VersionRange):
181
193
  # If the two ranges don't overlap, we won't be able to create a single
182
194
  # VersionRange for both of them.
183
- edges_touch = (self.max == other.min and (self.include_max or other.include_min)) or (
184
- self.min == other.max and (self.include_min or other.include_max)
185
- )
195
+ edges_touch = (
196
+ self.max == other.min and (self.include_max or other.include_min)
197
+ ) or (self.min == other.max and (self.include_min or other.include_max))
186
198
 
187
199
  if not edges_touch and not self.allows_any(other):
188
200
  return VersionUnion.of(self, other)
@@ -210,7 +222,7 @@ class VersionRange(VersionConstraint):
210
222
 
211
223
  return VersionUnion.of(self, other)
212
224
 
213
- def difference(self, other): # type: (VersionConstraint) -> VersionConstraint
225
+ def difference(self, other: VersionConstraint) -> VersionConstraint:
214
226
  from .version import Version
215
227
 
216
228
  if other.is_empty():
@@ -245,14 +257,18 @@ class VersionRange(VersionConstraint):
245
257
  elif self.min == other.min:
246
258
  before = self.min
247
259
  else:
248
- before = VersionRange(self.min, other.min, self.include_min, not other.include_min)
260
+ before = VersionRange(
261
+ self.min, other.min, self.include_min, not other.include_min
262
+ )
249
263
 
250
264
  if not self.allows_higher(other):
251
265
  after = None
252
266
  elif self.max == other.max:
253
267
  after = self.max
254
268
  else:
255
- after = VersionRange(other.max, self.max, not other.include_max, self.include_max)
269
+ after = VersionRange(
270
+ other.max, self.max, not other.include_max, self.include_max
271
+ )
256
272
 
257
273
  if before is None and after is None:
258
274
  return EmptyConstraint()
@@ -265,7 +281,7 @@ class VersionRange(VersionConstraint):
265
281
 
266
282
  return VersionUnion.of(before, after)
267
283
  elif isinstance(other, VersionUnion):
268
- ranges = [] # type: List[VersionRange]
284
+ ranges: List[VersionRange] = []
269
285
  current = self
270
286
 
271
287
  for range in other.ranges:
@@ -296,7 +312,7 @@ class VersionRange(VersionConstraint):
296
312
 
297
313
  raise ValueError("Unknown VersionConstraint type {}.".format(other))
298
314
 
299
- def allows_lower(self, other): # type: (VersionRange) -> bool
315
+ def allows_lower(self, other: "VersionRange") -> bool:
300
316
  if self.min is None:
301
317
  return other.min is not None
302
318
 
@@ -311,7 +327,7 @@ class VersionRange(VersionConstraint):
311
327
 
312
328
  return self.include_min and not other.include_min
313
329
 
314
- def allows_higher(self, other): # type: (VersionRange) -> bool
330
+ def allows_higher(self, other: "VersionRange") -> bool:
315
331
  if self.max is None:
316
332
  return other.max is not None
317
333
 
@@ -326,7 +342,7 @@ class VersionRange(VersionConstraint):
326
342
 
327
343
  return self.include_max and not other.include_max
328
344
 
329
- def is_strictly_lower(self, other): # type: (VersionRange) -> bool
345
+ def is_strictly_lower(self, other: "VersionRange") -> bool:
330
346
  if self.max is None or other.min is None:
331
347
  return False
332
348
 
@@ -338,14 +354,19 @@ class VersionRange(VersionConstraint):
338
354
 
339
355
  return not self.include_max or not other.include_min
340
356
 
341
- def is_strictly_higher(self, other): # type: (VersionRange) -> bool
357
+ def is_strictly_higher(self, other: "VersionRange") -> bool:
342
358
  return other.is_strictly_lower(self)
343
359
 
344
- def is_adjacent_to(self, other): # type: (VersionRange) -> bool
360
+ def is_adjacent_to(self, other: "VersionRange") -> bool:
345
361
  if self.max != other.min:
346
362
  return False
347
363
 
348
- return self.include_max and not other.include_min or not self.include_max and other.include_min
364
+ return (
365
+ self.include_max
366
+ and not other.include_min
367
+ or not self.include_max
368
+ and other.include_min
369
+ )
349
370
 
350
371
  def __eq__(self, other):
351
372
  if not isinstance(other, VersionRange):
@@ -370,7 +391,7 @@ class VersionRange(VersionConstraint):
370
391
  def __ge__(self, other):
371
392
  return self._cmp(other) >= 0
372
393
 
373
- def _cmp(self, other): # type: (VersionRange) -> int
394
+ def _cmp(self, other: "VersionRange") -> int:
374
395
  if self.min is None:
375
396
  if other.min is None:
376
397
  return self._compare_max(other)
@@ -388,7 +409,7 @@ class VersionRange(VersionConstraint):
388
409
 
389
410
  return self._compare_max(other)
390
411
 
391
- def _compare_max(self, other): # type: (VersionRange) -> int
412
+ def _compare_max(self, other: "VersionRange") -> int:
392
413
  if self.max is None:
393
414
  if other.max is None:
394
415
  return 0
@@ -1,10 +1,13 @@
1
- from typing import List
1
+ from typing import TYPE_CHECKING, List
2
2
 
3
3
  import semver
4
4
 
5
5
  from .empty_constraint import EmptyConstraint
6
6
  from .version_constraint import VersionConstraint
7
7
 
8
+ if TYPE_CHECKING:
9
+ from .version_range import VersionRange
10
+
8
11
 
9
12
  class VersionUnion(VersionConstraint):
10
13
  """
@@ -57,7 +60,10 @@ class VersionUnion(VersionConstraint):
57
60
  merged = []
58
61
  for constraint in flattened:
59
62
  # Merge this constraint with the previous one, but only if they touch.
60
- if not merged or (not merged[-1].allows_any(constraint) and not merged[-1].is_adjacent_to(constraint)):
63
+ if not merged or (
64
+ not merged[-1].allows_any(constraint)
65
+ and not merged[-1].is_adjacent_to(constraint)
66
+ ):
61
67
  merged.append(constraint)
62
68
  else:
63
69
  merged[-1] = merged[-1].union(constraint)
@@ -73,10 +79,10 @@ class VersionUnion(VersionConstraint):
73
79
  def is_any(self):
74
80
  return False
75
81
 
76
- def allows(self, version): # type: (semver.Version) -> bool
82
+ def allows(self, version: semver.Version) -> bool:
77
83
  return any([constraint.allows(version) for constraint in self._ranges])
78
84
 
79
- def allows_all(self, other): # type: (VersionConstraint) -> bool
85
+ def allows_all(self, other: VersionConstraint) -> bool:
80
86
  our_ranges = iter(self._ranges)
81
87
  their_ranges = iter(self._ranges_for(other))
82
88
 
@@ -91,7 +97,7 @@ class VersionUnion(VersionConstraint):
91
97
 
92
98
  return their_current_range is None
93
99
 
94
- def allows_any(self, other): # type: (VersionConstraint) -> bool
100
+ def allows_any(self, other: VersionConstraint) -> bool:
95
101
  our_ranges = iter(self._ranges)
96
102
  their_ranges = iter(self._ranges_for(other))
97
103
 
@@ -109,7 +115,7 @@ class VersionUnion(VersionConstraint):
109
115
 
110
116
  return False
111
117
 
112
- def intersect(self, other): # type: (VersionConstraint) -> VersionConstraint
118
+ def intersect(self, other: VersionConstraint) -> VersionConstraint:
113
119
  our_ranges = iter(self._ranges)
114
120
  their_ranges = iter(self._ranges_for(other))
115
121
  new_ranges = []
@@ -130,10 +136,10 @@ class VersionUnion(VersionConstraint):
130
136
 
131
137
  return VersionUnion.of(*new_ranges)
132
138
 
133
- def union(self, other): # type: (VersionConstraint) -> VersionConstraint
139
+ def union(self, other: VersionConstraint) -> VersionConstraint:
134
140
  return VersionUnion.of(self, other)
135
141
 
136
- def difference(self, other): # type: (VersionConstraint) -> VersionConstraint
142
+ def difference(self, other: VersionConstraint) -> VersionConstraint:
137
143
  our_ranges = iter(self._ranges)
138
144
  their_ranges = iter(self._ranges_for(other))
139
145
  new_ranges = []
@@ -213,7 +219,7 @@ class VersionUnion(VersionConstraint):
213
219
 
214
220
  return VersionUnion.of(*new_ranges)
215
221
 
216
- def _ranges_for(self, constraint): # type: (VersionConstraint) -> List[semver.VersionRange]
222
+ def _ranges_for(self, constraint: VersionConstraint) -> List["VersionRange"]:
217
223
  from .version_range import VersionRange
218
224
 
219
225
  if constraint.is_empty():
@@ -227,7 +233,7 @@ class VersionUnion(VersionConstraint):
227
233
 
228
234
  raise ValueError("Unknown VersionConstraint type {}".format(constraint))
229
235
 
230
- def _excludes_single_version(self): # type: () -> bool
236
+ def _excludes_single_version(self) -> bool:
231
237
  from .version import Version
232
238
  from .version_range import VersionRange
233
239
 
@@ -8,6 +8,7 @@ we don't expect relative file paths to exist, for example. Note that the parsing
8
8
  is also intentionally more lenient - it is not our job to validate the requirements
9
9
  list.
10
10
  """
11
+
11
12
  import os
12
13
  import re
13
14
  from pathlib import Path
@@ -51,12 +52,18 @@ def _strip_fragment(urlparts):
51
52
 
52
53
  class DetectedRequirement:
53
54
  def __init__(
54
- self, name: str = None, url: str = None, requirement: Requirement = None, location_defined: Path = None
55
+ self,
56
+ name: str = None,
57
+ url: str = None,
58
+ requirement: Requirement = None,
59
+ location_defined: Path = None,
55
60
  ):
56
61
  if requirement is not None:
57
62
  self.name = requirement.name
58
63
  self.requirement = requirement
59
- self.version_specs = [(s.operator, s.version) for s in requirement.specifier]
64
+ self.version_specs = [
65
+ (s.operator, s.version) for s in requirement.specifier
66
+ ]
60
67
  self.url = None
61
68
  else:
62
69
  self.name = name
@@ -66,7 +73,9 @@ class DetectedRequirement:
66
73
  self.location_defined = location_defined
67
74
 
68
75
  def _format_specs(self) -> str:
69
- return ",".join(["%s%s" % (comp, version) for comp, version in self.version_specs])
76
+ return ",".join(
77
+ ["%s%s" % (comp, version) for comp, version in self.version_specs]
78
+ )
70
79
 
71
80
  def pip_format(self) -> str:
72
81
  if self.url:
@@ -95,7 +104,11 @@ class DetectedRequirement:
95
104
  return "<DetectedRequirement:%s>" % str(self)
96
105
 
97
106
  def __eq__(self, other):
98
- return self.name == other.name and self.url == other.url and self.version_specs == other.version_specs
107
+ return (
108
+ self.name == other.name
109
+ and self.url == other.url
110
+ and self.version_specs == other.version_specs
111
+ )
99
112
 
100
113
  def __gt__(self, other):
101
114
  return (self.name or "") > (other.name or "")
@@ -149,7 +162,9 @@ class DetectedRequirement:
149
162
  # this happens if the line is invalid
150
163
  return None
151
164
  else:
152
- return DetectedRequirement(requirement=req, location_defined=location_defined)
165
+ return DetectedRequirement(
166
+ requirement=req, location_defined=location_defined
167
+ )
153
168
 
154
169
  # otherwise, this is some kind of URL
155
170
  name = _parse_egg_name(url.fragment)
@@ -158,4 +173,6 @@ class DetectedRequirement:
158
173
  if vcs_scheme:
159
174
  url = "%s://%s" % (vcs_scheme, url)
160
175
 
161
- return DetectedRequirement(name=name, url=url, location_defined=location_defined)
176
+ return DetectedRequirement(
177
+ name=name, url=url, location_defined=location_defined
178
+ )
@@ -1,4 +1,3 @@
1
- import os
2
1
  import sys
3
2
  from pathlib import Path
4
3
  from typing import NoReturn
@@ -1,49 +0,0 @@
1
- [tool.poetry]
2
- name = "requirements-detector"
3
- version = "1.3.1"
4
- authors = ["Landscape.io <code@landscape.io>"]
5
- classifiers = [
6
- 'Development Status :: 5 - Production/Stable',
7
- 'Environment :: Console',
8
- 'Intended Audience :: Developers',
9
- 'Operating System :: Unix',
10
- 'Topic :: Software Development :: Quality Assurance',
11
- 'Programming Language :: Python :: 3.8',
12
- 'Programming Language :: Python :: 3.9',
13
- 'Programming Language :: Python :: 3.10',
14
- 'Programming Language :: Python :: 3.11',
15
- 'Programming Language :: Python :: 3.12',
16
- 'License :: OSI Approved :: MIT License',
17
- ]
18
- license = "MIT"
19
- keywords = ["python","requirements detector"]
20
- description = "Python tool to find and list requirements of a Python project"
21
- readme = "README.md"
22
- homepage = "https://github.com/landscapeio/requirements-detector"
23
- packages = [
24
- { include = "requirements_detector/"}
25
- ]
26
-
27
- [tool.poetry.scripts]
28
- detect-requirements = 'requirements_detector.run:run'
29
-
30
- [tool.poetry.dependencies]
31
- python = ">=3.8,<4.0"
32
- astroid = "^3.0"
33
- packaging = ">=21.3"
34
- toml = "^0.10.2"
35
- semver = "^3.0.0"
36
-
37
- [tool.poetry.dev-dependencies]
38
- tox = "^3.24.5"
39
- pre-commit = "^2.17.0"
40
- pytest = "^6.2.4"
41
- coverage = "^5.5"
42
- twine = "^3.8.0"
43
- pytest-benchmark = "^3.4.1"
44
- pytest-cov = "^2.12.1"
45
- types-toml = "^0.10.4"
46
-
47
- [build-system]
48
- requires = ["poetry-core>=1.0.0"]
49
- build-backend = "poetry.core.masonry.api"
@@ -1,27 +0,0 @@
1
- import semver
2
-
3
-
4
- class VersionConstraint:
5
- def is_empty(self): # type: () -> bool
6
- raise NotImplementedError()
7
-
8
- def is_any(self): # type: () -> bool
9
- raise NotImplementedError()
10
-
11
- def allows(self, version): # type: (semver.Version) -> bool
12
- raise NotImplementedError()
13
-
14
- def allows_all(self, other): # type: (VersionConstraint) -> bool
15
- raise NotImplementedError()
16
-
17
- def allows_any(self, other): # type: (VersionConstraint) -> bool
18
- raise NotImplementedError()
19
-
20
- def intersect(self, other): # type: (VersionConstraint) -> VersionConstraint
21
- raise NotImplementedError()
22
-
23
- def union(self, other): # type: (VersionConstraint) -> VersionConstraint
24
- raise NotImplementedError()
25
-
26
- def difference(self, other): # type: (VersionConstraint) -> VersionConstraint
27
- raise NotImplementedError()