Skip to content

Commit 65069f1

Browse files
authored
Feature/382 clean up for nox task dependency changes (#446)
* Remove `tool.poetry.dev.dependencies` from nox task `dependency:licenses` as not poetry 2.x+ compatible * Get groups for direct dependencies from pyproject.toml * Extract all dependencies from poetry show to divide into direct & transitive * Use updated pydantic models in license code * Split up licenses and shared model to appropriate test and files * Move some of the mappings to be used in the pydantic model * Add pydantic to main dependencies * Update to pyupgrades to 3.9 but skip typing changes as incorrectly putting Optional[str], etc. to str | None for python3.9 and fix linting issues
1 parent 097f0be commit 65069f1

File tree

18 files changed

+698
-401
lines changed

18 files changed

+698
-401
lines changed

doc/changes/unreleased.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,21 @@
55
With #441, please ensure that the location of the `version.py` is given for `Config.version_file`,
66
which is specified in the `noxconfig.py`
77

8+
With #449, it's possible to customize what arguments are being using with `pyupgrade`
9+
via the `noxconfig.Config`:
10+
```python
11+
pyupgrade_args = ("--py310-plus",)
12+
```
13+
814
## 📚 Documentation
915
* Updated getting_started.rst for allowing tag-based releases
1016

1117
## ✨ Features
1218

13-
* [#441](https://github.com/exasol/python-toolbox/issues/441): Switched nox task for `version:check` to use the config value of `version_file` to specify the location of the `version.py`
19+
* [#441](https://github.com/exasol/python-toolbox/issues/441): Switched nox task for `version:check` to use the config value of `version_file` to specify the location of the `version.py`
20+
21+
## ⚒️ Refactorings
22+
23+
* [#449](https://github.com/exasol/python-toolbox/issues/449): Refactored `dependency:licenses`:
24+
* to use pydantic models & `poetry show` for dependencies
25+
* to updated reading the `pyproject.toml` to be compatible with poetry 2.x+

exasol/toolbox/nox/_dependencies.py

Lines changed: 15 additions & 207 deletions
Original file line numberDiff line numberDiff line change
@@ -3,214 +3,19 @@
33
import argparse
44
import json
55
import subprocess
6-
import tempfile
7-
from dataclasses import dataclass
8-
from inspect import cleandoc
9-
from json import loads
106
from pathlib import Path
117

128
import nox
13-
import tomlkit
149
from nox import Session
1510

16-
17-
@dataclass(frozen=True)
18-
class Package:
19-
name: str
20-
package_link: str
21-
version: str
22-
license: str
23-
license_link: str
24-
25-
26-
def _dependencies(toml_str: str) -> dict[str, list]:
27-
toml = tomlkit.loads(toml_str)
28-
poetry = toml.get("tool", {}).get("poetry", {})
29-
dependencies: dict[str, list] = {}
30-
31-
packages = poetry.get("dependencies", {})
32-
if packages:
33-
dependencies["project"] = []
34-
for package in packages:
35-
dependencies["project"].append(package)
36-
37-
packages = poetry.get("dev", {}).get("dependencies", {})
38-
if packages:
39-
dependencies["dev"] = []
40-
for package in packages:
41-
dependencies["dev"].append(package)
42-
43-
groups = poetry.get("group", {})
44-
for group in groups:
45-
packages = groups.get(group, {}).get("dependencies")
46-
if packages and not dependencies.get(group, {}):
47-
dependencies[group] = []
48-
for package in packages:
49-
dependencies[group].append(package)
50-
return dependencies
51-
52-
53-
def _normalize(_license: str) -> str:
54-
def is_multi_license(l):
55-
return ";" in l
56-
57-
def select_most_restrictive(licenses: list) -> str:
58-
_max = 0
59-
lic = "Unknown"
60-
_mapping = {
61-
"Unknown": -1,
62-
"Unlicensed": 0,
63-
"BSD": 1,
64-
"MIT": 2,
65-
"MPLv2": 3,
66-
"LGPLv2": 4,
67-
"GPLv2": 5,
68-
"GPLv3": 6,
69-
}
70-
for l in licenses:
71-
if l in _mapping:
72-
if _mapping[l] > _mapping[lic]:
73-
lic = l
74-
else:
75-
return "<br>".join(licenses)
76-
return lic
77-
78-
mapping = {
79-
"BSD License": "BSD",
80-
"MIT License": "MIT",
81-
"The Unlicensed (Unlicensed)": "Unlicensed",
82-
"Mozilla Public License 2.0 (MPL 2.0)": "MPLv2",
83-
"GNU General Public License (GPL)": "GPL",
84-
"GNU Lesser General Public License v2 (LGPLv2)": "LGPLv2",
85-
"GNU General Public License v2 (GPLv2)": "GPLv2",
86-
"GNU General Public License v2 or later (GPLv2+)": "GPLv2+",
87-
"GNU General Public License v3 (GPLv3)": "GPLv3",
88-
"Apache Software License": "Apache",
89-
}
90-
91-
if is_multi_license(_license):
92-
items = []
93-
for item in _license.split(";"):
94-
item = str(item).strip()
95-
if item in mapping:
96-
items.append(mapping[item])
97-
else:
98-
items.append(item)
99-
return select_most_restrictive(items)
100-
101-
if _license not in mapping:
102-
return _license
103-
104-
return mapping[_license]
105-
106-
107-
def _packages_from_json(json: str) -> list[Package]:
108-
packages = loads(json)
109-
packages_list = []
110-
mapping = {
111-
"GPLv1": "https://www.gnu.org/licenses/old-licenses/gpl-1.0.html",
112-
"GPLv2": "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html",
113-
"LGPLv2": "https://www.gnu.org/licenses/old-licenses/lgpl-2.0.html",
114-
"GPLv3": "https://www.gnu.org/licenses/gpl-3.0.html",
115-
"LGPLv3": "https://www.gnu.org/licenses/lgpl-3.0.html",
116-
"Apache": "https://www.apache.org/licenses/LICENSE-2.0",
117-
"MIT": "https://mit-license.org/",
118-
"BSD": "https://opensource.org/license/bsd-3-clause",
119-
}
120-
for package in packages:
121-
package_license = _normalize(package["License"])
122-
packages_list.append(
123-
Package(
124-
name=package["Name"],
125-
package_link="" if package["URL"] == "UNKNOWN" else package["URL"],
126-
version=package["Version"],
127-
license=package_license,
128-
license_link=(
129-
"" if package_license not in mapping else mapping[package_license]
130-
),
131-
)
132-
)
133-
return packages_list
134-
135-
136-
def _licenses() -> list[Package]:
137-
with tempfile.NamedTemporaryFile() as file:
138-
subprocess.run(
139-
[
140-
"poetry",
141-
"run",
142-
"pip-licenses",
143-
"--format=json",
144-
"--output-file=" + file.name,
145-
"--with-system",
146-
"--with-urls",
147-
],
148-
capture_output=True,
149-
)
150-
result = _packages_from_json(file.read().decode())
151-
return result
152-
153-
154-
def _packages_to_markdown(
155-
dependencies: dict[str, list], packages: list[Package]
156-
) -> str:
157-
def heading():
158-
text = "# Dependencies\n"
159-
return text
160-
161-
def dependency(group: str, group_packages: list, packages: list[Package]) -> str:
162-
def _header(_group: str):
163-
_group = "".join([word.capitalize() for word in _group.strip().split()])
164-
text = f"## {_group} Dependencies\n"
165-
text += "|Package|version|Licence|\n"
166-
text += "|---|---|---|\n"
167-
return text
168-
169-
def _rows(_group_packages: list, _packages: list[Package]) -> str:
170-
def _normalize_package_name(name: str) -> str:
171-
_name = name.lower()
172-
while "_" in _name:
173-
_name = _name.replace("_", "-")
174-
return _name
175-
176-
text = ""
177-
for package in _group_packages:
178-
consistent = filter(
179-
lambda elem: (_normalize_package_name(elem.name) == package),
180-
_packages,
181-
)
182-
for content in consistent:
183-
if content.package_link:
184-
text += f"|[{content.name}]({content.package_link})"
185-
else:
186-
text += f"|{content.name}"
187-
text += f"|{content.version}"
188-
if content.license_link:
189-
text += f"|[{content.license}]({content.license_link})|\n"
190-
else:
191-
text += f"|{content.license}|\n"
192-
text += "\n"
193-
return text
194-
195-
_template = cleandoc(
196-
"""
197-
{header}{rows}
198-
"""
199-
)
200-
return _template.format(
201-
header=_header(group), rows=_rows(group_packages, packages)
202-
)
203-
204-
template = cleandoc(
205-
"""
206-
{heading}{rows}
207-
"""
208-
)
209-
210-
rows = ""
211-
for group in dependencies:
212-
rows += dependency(group, dependencies[group], packages)
213-
return template.format(heading=heading(), rows=rows)
11+
from exasol.toolbox.util.dependencies.licenses import (
12+
licenses,
13+
packages_to_markdown,
14+
)
15+
from exasol.toolbox.util.dependencies.poetry_dependencies import (
16+
PoetryDependencies,
17+
PoetryToml,
18+
)
21419

21520

21621
class Audit:
@@ -282,10 +87,13 @@ def run(self, session: Session) -> None:
28287
@nox.session(name="dependency:licenses", python=False)
28388
def dependency_licenses(session: Session) -> None:
28489
"""returns the packages and their licenses"""
285-
toml = Path("pyproject.toml")
286-
dependencies = _dependencies(toml.read_text())
287-
package_infos = _licenses()
288-
print(_packages_to_markdown(dependencies=dependencies, packages=package_infos))
90+
working_directory = Path()
91+
poetry_dep = PoetryToml.load_from_toml(working_directory=working_directory)
92+
dependencies = PoetryDependencies(
93+
groups=poetry_dep.groups, working_directory=working_directory
94+
).direct_dependencies
95+
package_infos = licenses()
96+
print(packages_to_markdown(dependencies=dependencies, packages=package_infos))
28997

29098

29199
@nox.session(name="dependency:audit", python=False)

exasol/toolbox/nox/_format.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import Iterable
3+
from collections.abc import Iterable
44

55
import nox
66
from nox import Session
@@ -10,7 +10,12 @@
1010
_version,
1111
python_files,
1212
)
13-
from noxconfig import PROJECT_CONFIG
13+
from noxconfig import (
14+
PROJECT_CONFIG,
15+
Config,
16+
)
17+
18+
_PYUPGRADE_ARGS = ("--py39-plus",)
1419

1520

1621
def _code_format(session: Session, mode: Mode, files: Iterable[str]) -> None:
@@ -21,12 +26,11 @@ def command(*args: str) -> Iterable[str]:
2126
session.run(*command("black"), *files)
2227

2328

24-
def _pyupgrade(session: Session, files: Iterable[str]) -> None:
29+
def _pyupgrade(session: Session, config: Config, files: Iterable[str]) -> None:
30+
pyupgrade_args = getattr(config, "pyupgrade_args", _PYUPGRADE_ARGS)
2531
session.run(
26-
"poetry",
27-
"run",
2832
"pyupgrade",
29-
"--py38-plus",
33+
*pyupgrade_args,
3034
"--exit-zero-even-if-changed",
3135
*files,
3236
)
@@ -37,7 +41,7 @@ def fix(session: Session) -> None:
3741
"""Runs all automated fixes on the code base"""
3842
py_files = [f"{file}" for file in python_files(PROJECT_CONFIG.root)]
3943
_version(session, Mode.Fix)
40-
_pyupgrade(session, py_files)
44+
_pyupgrade(session, config=PROJECT_CONFIG, files=py_files)
4145
_code_format(session, Mode.Fix, py_files)
4246

4347

exasol/toolbox/nox/_lint.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@
22

33
import argparse
44
import sys
5+
from collections.abc import Iterable
56
from pathlib import Path
6-
from typing import (
7-
Dict,
8-
Iterable,
9-
List,
10-
)
117

128
import nox
139
import rich.console
@@ -20,10 +16,6 @@
2016

2117
def _pylint(session: Session, files: Iterable[str]) -> None:
2218
session.run(
23-
"poetry",
24-
"run",
25-
"python",
26-
"-m",
2719
"pylint",
2820
"--output-format",
2921
"colorized,json:.lint.json,text:.lint.txt",
@@ -33,8 +25,6 @@ def _pylint(session: Session, files: Iterable[str]) -> None:
3325

3426
def _type_check(session: Session, files: Iterable[str]) -> None:
3527
session.run(
36-
"poetry",
37-
"run",
3828
"mypy",
3929
"--explicit-package-bases",
4030
"--namespace-packages",
@@ -49,8 +39,6 @@ def _type_check(session: Session, files: Iterable[str]) -> None:
4939

5040
def _security_lint(session: Session, files: Iterable[str]) -> None:
5141
session.run(
52-
"poetry",
53-
"run",
5442
"bandit",
5543
"--severity-level",
5644
"low",
@@ -63,8 +51,6 @@ def _security_lint(session: Session, files: Iterable[str]) -> None:
6351
*files,
6452
)
6553
session.run(
66-
"poetry",
67-
"run",
6854
"bandit",
6955
"--severity-level",
7056
"low",

exasol/toolbox/nox/tasks.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
from exasol.toolbox.nox._format import (
2323
_code_format,
24-
_pyupgrade,
2524
fix,
2625
)
2726

exasol/toolbox/tools/security.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from functools import partial
1919
from inspect import cleandoc
2020
from pathlib import Path
21+
from typing import Optional
2122

2223
import typer
2324

@@ -269,7 +270,9 @@ def as_markdown_listing(elements: Iterable[str]):
269270
)
270271

271272

272-
def create_security_issue(issue: Issue, project: str | None = None) -> tuple[str, str]:
273+
def create_security_issue(
274+
issue: Issue, project: Optional[str] = None
275+
) -> tuple[str, str]:
273276
# fmt: off
274277
command = [
275278
"gh", "issue", "create",

exasol/toolbox/util/dependencies/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)