Skip to content

Commit a12cbec

Browse files
committed
rel 2023.4
1 parent 09f5b5d commit a12cbec

File tree

12 files changed

+110
-92
lines changed

12 files changed

+110
-92
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
All major and minor version changes will be documented in this file. Details of
44
patch-level version changes can be found in [commit messages](../../commits/master).
55

6+
## 2023.4 - 2023/08/20
7+
8+
- Refactor and code enhancements
9+
- Fixed Bug: fix behaviour of dependency discovery for 'extras', thank you https://github.com/TuriJ95!
10+
- Fixed Bug: ignore-package not working, thank you https://github.com/mathiasbockwoldt !
11+
612
## 2023.3 - 2023/07/29
713

814
- Fixed Bug: requirements:requirements.txt reading mode, thank you https://github.com/NicolaDonelli

documentation/reference/licensecheck/formatter.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,4 @@ Strip ansi codes from a given string
178178
```python
179179
def stripAnsi(string: str) -> str:
180180
...
181-
```
182-
183-
181+
```

documentation/reference/licensecheck/get_deps.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Get Deps
1212

1313
## getDepsWithLicenses
1414

15-
[Show source in get_deps.py:129](../../../licensecheck/get_deps.py#L129)
15+
[Show source in get_deps.py:157](../../../licensecheck/get_deps.py#L157)
1616

1717
Get a set of dependencies with licenses and determine license compatibility.
1818

@@ -53,7 +53,7 @@ def getDepsWithLicenses(
5353

5454
## getReqs
5555

56-
[Show source in get_deps.py:19](../../../licensecheck/get_deps.py#L19)
56+
[Show source in get_deps.py:20](../../../licensecheck/get_deps.py#L20)
5757

5858
Get requirements for the end user project/ lib.
5959

@@ -83,6 +83,4 @@ def getReqs(using: str) -> set[ucstr]:
8383

8484
#### See also
8585

86-
- [ucstr](./types.md#ucstr)
87-
88-
86+
- [ucstr](./types.md#ucstr)

documentation/reference/licensecheck/license_matrix.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,4 @@ def licenseType(lice: ucstr, ignoreLicenses: list[ucstr] | None = None) -> list[
132132
#### See also
133133

134134
- [License](./types.md#license)
135-
- [ucstr](./types.md#ucstr)
136-
137-
135+
- [ucstr](./types.md#ucstr)

documentation/reference/licensecheck/module.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@
55
Module
66

77
> Auto-generated documentation for [licensecheck.__main__](../../../licensecheck/__main__.py) module.
8-
98
- [Module](#module)

documentation/reference/licensecheck/packageinfo.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,4 @@ def licenseFromClassifierlist(classifiers: list[str]) -> ucstr:
204204

205205
#### See also
206206

207-
- [ucstr](./types.md#ucstr)
208-
209-
207+
- [ucstr](./types.md#ucstr)

documentation/reference/licensecheck/types.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Types
1414

1515
## License
1616

17-
[Show source in types.py:45](../../../licensecheck/types.py#L45)
17+
[Show source in types.py:46](../../../licensecheck/types.py#L46)
1818

1919
#### Attributes
2020

@@ -50,7 +50,7 @@ class License(Enum):
5050

5151
## PackageInfo
5252

53-
[Show source in types.py:28](../../../licensecheck/types.py#L28)
53+
[Show source in types.py:29](../../../licensecheck/types.py#L29)
5454

5555
PackageInfo type.
5656

@@ -65,7 +65,7 @@ class PackageInfo:
6565

6666
## ucstr
6767

68-
[Show source in types.py:16](../../../licensecheck/types.py#L16)
68+
[Show source in types.py:17](../../../licensecheck/types.py#L17)
6969

7070
Uppercase string
7171

@@ -80,7 +80,7 @@ class ucstr(str):
8080

8181
## printLicense
8282

83-
[Show source in types.py:88](../../../licensecheck/types.py#L88)
83+
[Show source in types.py:89](../../../licensecheck/types.py#L89)
8484

8585
Output a license as plain text
8686

@@ -102,6 +102,4 @@ def printLicense(licenseEnum: L) -> str:
102102

103103
#### See also
104104

105-
- [L](#l)
106-
107-
105+
- [L](#l)

licensecheck/get_deps.py

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
from pathlib import Path
88
from typing import Any
99

10-
import pkg_resources
11-
import requirements
1210
import tomli
11+
from packaging.requirements import Requirement
12+
from packaging.utils import canonicalize_name
1313

1414
from licensecheck import license_matrix, packageinfo
1515
from licensecheck.types import JOINS, License, PackageInfo, session, ucstr
@@ -56,11 +56,25 @@ def getReqs(using: str) -> set[ucstr]:
5656
def _doGetReqs(
5757
using: str, extras: str | None, pyproject: dict[str, Any], requirementsPaths: list[Path]
5858
) -> set[ucstr]:
59-
resolveReq = lambda req: ucstr(pkg_resources.Requirement.parse(req).project_name)
60-
resolveExtraReq = lambda extra_req: re.sub("extra == ", "", re.findall(r"extra == '.*?'",extra_req)[0].replace("'", "")) if len(re.findall(r"extra == '.*?'",extra_req)) > 0 else None
6159

6260
reqs = set()
63-
extras_reqs = dict()
61+
extrasReqs = {}
62+
63+
def resolveReq(req: str, extra: bool = True) -> ucstr:
64+
requirement = Requirement(req)
65+
extras = {ucstr(extra) for extra in requirement.extras}
66+
name = ucstr(canonicalize_name(requirement.name))
67+
canonicalName = name
68+
if len(extras) > 0:
69+
canonicalName = ucstr(f"{name}[{list(extras)[0]}]")
70+
extrasReqs[name] = extras
71+
return canonicalName if extra else name
72+
73+
def resolveExtraReq(extraReq: str) -> ucstr | None:
74+
match = re.search(r"extra\s*==\s*'(.*?)'", extraReq)
75+
if match is None:
76+
return None
77+
return ucstr(match.group(1))
6478

6579
if using == "poetry":
6680
try:
@@ -78,12 +92,7 @@ def _doGetReqs(
7892
reqLists.append(project.get("dev-dependencies", {}))
7993
for reqList in reqLists:
8094
for req in reqList:
81-
try:
82-
extra = re.search(r'(?<=\[)(.*?)(?=\])',req).group(0)
83-
extras_reqs[resolveReq(req)] = extra
84-
reqs.add(f"{resolveReq(req)}[{extra}]")
85-
except (KeyError, AttributeError):
86-
reqs.add(resolveReq(req))
95+
reqs.add(resolveReq(req))
8796
# PEP631 (hatch)
8897
if using == "PEP631":
8998
try:
@@ -97,27 +106,19 @@ def _doGetReqs(
97106
reqLists.extend(project["optional-dependencies"][x] for x in extras.split(";"))
98107
for reqList in reqLists:
99108
for req in reqList:
100-
try:
101-
extra = re.search(r'(?<=\[)(.*?)(?=\])',req).group(0)
102-
extras_reqs[resolveReq(req)] = extra
103-
reqs.add(f"{resolveReq(req)}[{extra}]")
104-
except (KeyError, AttributeError):
105-
reqs.add(resolveReq(req))
109+
reqs.add(resolveReq(req))
106110

107111
# Requirements
108112
if using == "requirements":
109113
for reqPath in requirementsPaths:
110114
if not reqPath.exists():
111115
raise RuntimeError(f"Could not find specification of requirements ({reqPath}).")
112116

113-
with open(reqPath, encoding="utf-8") as requirementsTxt:
114-
for req in requirements.parse(requirementsTxt):
115-
if len(req.extras)>0:
116-
extras_reqs[resolveReq(req.name)] = req.extras
117-
for extra in req.extras:
118-
reqs.add(f"{resolveReq(req.name)}[{extra}]")
119-
else:
120-
reqs.add(resolveReq(req.name))
117+
for line in reqPath.read_text(encoding="utf-8").splitlines():
118+
line = line.strip()
119+
if not line or line[0] in {"#", "-"}:
120+
continue
121+
reqs.add(resolveReq(line))
121122

122123
try:
123124
reqs.remove("PYTHON")
@@ -132,19 +133,26 @@ def _doGetReqs(
132133
for req in [resolveReq(req) for req in pkgMetadata.get_all("Requires-Dist") or []]:
133134
requirementsWithDeps.add(req)
134135
except metadata.PackageNotFoundError:
135-
request = session.get(f"https://pypi.org/pypi/{requirement.split('[')[0]}/json", timeout=60)
136+
request = session.get(
137+
f"https://pypi.org/pypi/{requirement.split('[')[0]}/json", timeout=60
138+
)
136139
response = request.json()
137140
try:
138141
for dependency in response["info"]["requires_dist"]:
139-
if resolveExtraReq(dependency) is not None:
140-
if (resolveReq(requirement) in extras_reqs.keys()) and (resolveExtraReq(dependency) in extras_reqs[resolveReq(requirement)]):
141-
requirementsWithDeps.add(resolveReq(dependency))
142+
dep = resolveReq(dependency, False)
143+
req = resolveReq(requirement, False)
144+
extra = resolveExtraReq(dependency)
145+
if extra is not None:
146+
if req in extrasReqs and extra in extrasReqs.get(req, []):
147+
requirementsWithDeps.add(dep)
148+
# else: pass
142149
else:
143-
requirementsWithDeps.add(resolveReq(dependency))
150+
requirementsWithDeps.add(dep)
144151
except (KeyError, TypeError):
145152
pass
146153

147-
return {r.split('[')[0] for r in requirementsWithDeps}
154+
return {r.split("[")[0] for r in requirementsWithDeps}
155+
148156

149157
def getDepsWithLicenses(
150158
using: str,

licensecheck/types.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
from enum import Enum
77
from pathlib import Path
88

9+
import requests_cache
10+
911
THISDIR = Path(__file__).resolve().parent
1012

11-
import requests_cache
1213

1314
session = requests_cache.CachedSession(f"{THISDIR}/licensecheck")
1415

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "licensecheck"
3-
version = "2023.3"
3+
version = "2023.4"
44
license = "mit"
55
description = "Output the licenses used by dependencies and check if these are compatible with the project license"
66
authors = ["FredHappyface"]
@@ -28,12 +28,12 @@ licensecheck = 'licensecheck:cli'
2828

2929
[tool.poetry.dependencies]
3030
python = "^3.8"
31-
requirements-parser = "<2,>=0.5.0"
3231
requests = "<3,>=2.31.0"
3332
fhconfparser = "<2024,>=2022"
3433
tomli = "<3,>=2.0.1"
3534
rich = "<14,>=13.4.2"
3635
requests-cache = "<2,>=1.1.0"
36+
packaging = "<24,>=23.1"
3737

3838
[tool.poetry.dev-dependencies]
3939
pytest = "^7.1.1"

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
fhconfparser<2024,>=2022
2+
packaging<24,>=23.1
23
requests-cache<2,>=1.1.0
34
requests<3,>=2.31.0
4-
requirements-parser<2,>=0.5.0
55
rich<14,>=13.4.2
66
tomli<3,>=2.0.1

tests/test_get_deps.py

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,47 +15,61 @@ def test_doGetReqs_PEP631():
1515
requirementsPaths = []
1616

1717
assert get_deps._doGetReqs(using, extras, pyproject, requirementsPaths) == {
18-
'DOCKERPTY',
19-
'PACKAGING',
20-
'ATTRS',
21-
'JSONSCHEMA',
22-
'PYYAML',
23-
'PYSOCKS',
24-
'CERTIFI',
25-
'ENUM34',
26-
'DOCKER',
27-
'TEXTTABLE',
28-
'PYWIN32',
29-
'JSONSCHEMA-SPECIFICATIONS',
30-
'IPADDRESS',
31-
'PKGUTIL-RESOLVE-NAME',
32-
'DOCOPT',
33-
'BACKPORTS.SSL-MATCH-HOSTNAME',
34-
'PARAMIKO',
35-
'IDNA',
36-
'COLORAMA',
37-
'IMPORTLIB-RESOURCES',
38-
'CACHED-PROPERTY',
39-
'DISTRO',
40-
'BACKPORTS.SHUTIL-GET-TERMINAL-SIZE',
41-
'CHARSET-NORMALIZER',
42-
'URLLIB3',
43-
'WEBSOCKET-CLIENT',
44-
'RPDS-PY',
45-
'SUBPROCESS32',
46-
'REQUESTS',
47-
'REFERENCING',
48-
'CHARDET',
49-
'PYTHON-DOTENV'
18+
"DOCKERPTY",
19+
"PACKAGING",
20+
"ATTRS",
21+
"JSONSCHEMA",
22+
"PYYAML",
23+
"PYSOCKS",
24+
"CERTIFI",
25+
"ENUM34",
26+
"DOCKER",
27+
"TEXTTABLE",
28+
"PYWIN32",
29+
"JSONSCHEMA-SPECIFICATIONS",
30+
"IPADDRESS",
31+
"PKGUTIL-RESOLVE-NAME",
32+
"DOCOPT",
33+
"BACKPORTS-SSL-MATCH-HOSTNAME",
34+
"PARAMIKO",
35+
"IDNA",
36+
"COLORAMA",
37+
"IMPORTLIB-RESOURCES",
38+
"CACHED-PROPERTY",
39+
"DISTRO",
40+
"BACKPORTS-SHUTIL-GET-TERMINAL-SIZE",
41+
"CHARSET-NORMALIZER",
42+
"URLLIB3",
43+
"WEBSOCKET-CLIENT",
44+
"RPDS-PY",
45+
"SUBPROCESS32",
46+
"REQUESTS",
47+
"REFERENCING",
48+
"CHARDET",
49+
"PYTHON-DOTENV",
5050
}
5151

52+
5253
def test_doGetReqs_requirements():
5354

5455
using = "requirements"
5556
extras = f"{THISDIR}/data/test_requirements.txt"
5657
pyproject = {}
5758
requirementsPaths = [Path(f"{THISDIR}/data/test_requirements.txt")]
5859
deps = get_deps._doGetReqs(using, extras, pyproject, requirementsPaths)
59-
assert deps == {'NUMPY', 'ODFPY', 'OPENPYXL', 'PANDAS', 'PYTHON-DATEUTIL', 'PYTZ', 'PYXLSB', 'TZDATA', 'XLRD', 'XLSXWRITER'}
60-
assert 'OPENPYXL' in deps
61-
assert 'XARRAY' not in deps #xarray is an optional dependecy of pandas associated with 'computation' key that is not tracked in test_requirements.txt
60+
assert deps == {
61+
"NUMPY",
62+
"ODFPY",
63+
"OPENPYXL",
64+
"PANDAS",
65+
"PYTHON-DATEUTIL",
66+
"PYTZ",
67+
"PYXLSB",
68+
"TZDATA",
69+
"XLRD",
70+
"XLSXWRITER",
71+
}
72+
assert "OPENPYXL" in deps
73+
assert (
74+
"XARRAY" not in deps
75+
) # xarray is an optional dependecy of pandas associated with 'computation' key that is not tracked in test_requirements.txt

0 commit comments

Comments
 (0)