Skip to content

Commit 07ebedc

Browse files
jkowalleckmadpah
andauthored
ci: update to run tox for both our favoured versions of dependencies and lowest supported versions
* add tox env for minimal required dependencies Signed-off-by: Jan Kowalleck <[email protected]> * try to fix `TypedDict` typing Signed-off-by: Jan Kowalleck <[email protected]> * fix: typing definitions to be PY 3.6 compatible Signed-off-by: Paul Horton <[email protected]> * fix: typing definitions to be PY 3.6 compatible Signed-off-by: Paul Horton <[email protected]> * straigtened up `sys.version_info` constraints/code-branches Signed-off-by: Jan Kowalleck <[email protected]> * removed unused type ignores Signed-off-by: Jan Kowalleck <[email protected]> * try to fix type variants Signed-off-by: Jan Kowalleck <[email protected]> * try to fix type variants Signed-off-by: Jan Kowalleck <[email protected]> * typing for py3.6 Signed-off-by: Paul Horton <[email protected]> * fixed invalid unittest Signed-off-by: Paul Horton <[email protected]> * typing for py3.6 Signed-off-by: Jan Kowalleck <[email protected]> * mypy silence `warn_unused_ignores` Signed-off-by: Jan Kowalleck <[email protected]> * mypy in tox for lowest version is pinned Signed-off-by: Jan Kowalleck <[email protected]> Co-authored-by: Paul Horton <[email protected]>
1 parent 20035bb commit 07ebedc

File tree

10 files changed

+92
-59
lines changed

10 files changed

+92
-59
lines changed

.github/workflows/poetry.yml

+35-17
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ on:
1414

1515
env:
1616
REPORTS_DIR: CI_reports
17+
PYTHON_VERISON_DEFAULT: "3.10"
18+
POETRY_VERSION: "1.1.11"
1719

1820
jobs:
1921
coding-standards:
20-
name: Linting & Coding Standards
22+
name: Linting & CodingStandards
2123
runs-on: ubuntu-latest
2224
steps:
2325
- name: Checkout
@@ -27,25 +29,35 @@ jobs:
2729
# see https://github.com/actions/setup-python
2830
uses: actions/setup-python@v2
2931
with:
30-
python-version: 3.9
32+
python-version: ${{ env.PYTHON_VERISON_DEFAULT }}
3133
architecture: 'x64'
3234
- name: Install poetry
3335
# see https://github.com/marketplace/actions/setup-poetry
3436
uses: Gr1N/setup-poetry@v7
3537
with:
36-
poetry-version: 1.1.8
38+
poetry-version: ${{ env.POETRY_VERSION }}
3739
- uses: actions/cache@v2
3840
with:
3941
path: ~/.cache/pypoetry/virtualenvs
40-
key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }}
42+
key: ${{ runner.os }}-${{ env.PYTHON_VERISON_DEFAULT }}-poetry${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }}
4143
- name: Install dependencies
42-
run: poetry install
44+
run: poetry install --no-root
4345
- name: Run tox
4446
run: poetry run tox -e flake8
4547

4648
static-code-analysis:
47-
name: Static Coding Analysis
49+
name: StaticCodingAnalysis (py${{ matrix.python-version}} ${{ matrix.toxenv-factor }})
4850
runs-on: ubuntu-latest
51+
strategy:
52+
fail-fast: false
53+
matrix:
54+
include:
55+
- # test with the locked dependencies
56+
python-version: '3.10'
57+
toxenv-factor: 'locked'
58+
- # test with the lowest dependencies
59+
python-version: '3.6'
60+
toxenv-factor: 'lowest'
4961
steps:
5062
- name: Checkout
5163
# see https://github.com/actions/checkout
@@ -54,37 +66,43 @@ jobs:
5466
# see https://github.com/actions/setup-python
5567
uses: actions/setup-python@v2
5668
with:
57-
python-version: 3.9
69+
python-version: ${{ matrix.python-version }}
5870
architecture: 'x64'
5971
- name: Install poetry
6072
# see https://github.com/marketplace/actions/setup-poetry
6173
uses: Gr1N/setup-poetry@v7
6274
with:
63-
poetry-version: 1.1.8
75+
poetry-version: ${{ env.POETRY_VERSION }}
6476
- uses: actions/cache@v2
6577
with:
6678
path: ~/.cache/pypoetry/virtualenvs
67-
key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }}
79+
key: ${{ runner.os }}-${{ matrix.python-version }}-poetry${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }}
6880
- name: Install dependencies
69-
run: poetry install
81+
run: poetry install --no-root
7082
- name: Run tox
71-
run: poetry run tox -e mypy
83+
run: poetry run tox -e mypy-${{ matrix.toxenv-factor }}
7284

7385
build-and-test:
74-
name: Build & Test Python ${{ matrix.python-version }} on ${{ matrix.os }}
86+
name: Test (${{ matrix.os }} py${{ matrix.python-version }} ${{ matrix.toxenv-factor }})
7587
runs-on: ${{ matrix.os }}
7688
env:
7789
REPORTS_ARTIFACT: tests-reports
7890
strategy:
7991
fail-fast: false
8092
matrix:
81-
os: [ubuntu-latest, windows-latest, macos-latest]
93+
os: ['ubuntu-latest', 'windows-latest', 'macos-latest']
8294
python-version:
8395
- "3.10" # highest supported
8496
- "3.9"
8597
- "3.8"
8698
- "3.7"
8799
- "3.6" # lowest supported
100+
toxenv-factor: ['locked']
101+
include:
102+
- # test with the lowest dependencies
103+
os: 'ubuntu-latest'
104+
python-version: '3.6'
105+
toxenv: 'lowest'
88106
timeout-minutes: 30
89107
steps:
90108
- name: Disabled Git auto EOL CRLF transforms
@@ -108,17 +126,17 @@ jobs:
108126
# see https://github.com/marketplace/actions/setup-poetry
109127
uses: Gr1N/setup-poetry@v7
110128
with:
111-
poetry-version: 1.1.11
129+
poetry-version: ${{ env.POETRY_VERSION }}
112130
- uses: actions/cache@v2
113131
with:
114132
path: ~/.cache/pypoetry/virtualenvs
115-
key: ${{ runner.os }}}-${{ matrix.python-version }}-poetry-${{ hashFiles('poetry.lock') }}
133+
key: ${{ runner.os }}}-${{ matrix.python-version }}-poetry${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }}
116134
- name: Install dependencies
117-
run: poetry install
135+
run: poetry install --no-root
118136
- name: Ensure build successful
119137
run: poetry build
120138
- name: Run tox
121-
run: poetry run tox -e py -s false
139+
run: poetry run tox -e py-${{ matrix.toxenv-factor }} -s false
122140
- name: Generate coverage reports
123141
run: >
124142
poetry run coverage report &&

.mypy.ini

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ check_untyped_defs = True
2323
disallow_untyped_decorators = True
2424
no_implicit_optional = True
2525
warn_redundant_casts = True
26-
warn_unused_ignores = True
2726
warn_return_any = True
2827
no_implicit_reexport = True
2928

29+
# needed to silence some py37|py38 differences
30+
warn_unused_ignores = False
31+
3032
[mypy-pytest.*]
3133
ignore_missing_imports = True
3234

cyclonedx/model/bom.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,16 @@ def __repr__(self) -> str:
8383
return '<Tool {}:{}:{}>'.format(self._vendor, self._name, self._version)
8484

8585

86-
if sys.version_info >= (3, 8, 0):
86+
if sys.version_info >= (3, 8):
8787
from importlib.metadata import version as meta_version
8888
else:
89-
from importlib_metadata import version as meta_version # type: ignore
89+
from importlib_metadata import version as meta_version
9090

9191
try:
92-
ThisTool = Tool(vendor='CycloneDX', name='cyclonedx-python-lib', version=meta_version('cyclonedx-python-lib'))
92+
__ThisToolVersion: Optional[str] = str(meta_version('cyclonedx-python-lib')) # type: ignore[no-untyped-call]
9393
except Exception:
94-
ThisTool = Tool(vendor='CycloneDX', name='cyclonedx-python-lib', version='UNKNOWN')
94+
__ThisToolVersion = None
95+
ThisTool = Tool(vendor='CycloneDX', name='cyclonedx-python-lib', version=__ThisToolVersion or 'UNKNOWN')
9596

9697

9798
class BomMetaData:

cyclonedx/output/json.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# Copyright (c) OWASP Foundation. All Rights Reserved.
1919

2020
import json
21-
from typing import Union
21+
from typing import Dict, List, Union
2222

2323
from . import BaseOutput
2424
from .schema import BaseSchemaVersion, SchemaVersion1Dot0, SchemaVersion1Dot1, SchemaVersion1Dot2, SchemaVersion1Dot3
@@ -47,8 +47,8 @@ def _get_json(self) -> object:
4747
return response
4848

4949
def _get_component_as_dict(self, component: Component) -> object:
50-
c: dict[str, Union[str, list[dict[str, str]], list[dict[str, dict[str, str]]], list[
51-
dict[str, Union[str, list[dict[str, str]]]]]]] = {
50+
c: Dict[str, Union[str, List[Dict[str, str]], List[Dict[str, Dict[str, str]]], List[
51+
Dict[str, Union[str, List[Dict[str, str]]]]]]] = {
5252
"type": component.get_type().value,
5353
"name": component.get_name(),
5454
"version": component.get_version(),
@@ -59,7 +59,7 @@ def _get_component_as_dict(self, component: Component) -> object:
5959
c['group'] = str(component.get_namespace())
6060

6161
if component.get_hashes():
62-
hashes: list[dict[str, str]] = []
62+
hashes: List[Dict[str, str]] = []
6363
for component_hash in component.get_hashes():
6464
hashes.append({
6565
"alg": component_hash.get_algorithm().value,
@@ -68,7 +68,7 @@ def _get_component_as_dict(self, component: Component) -> object:
6868
c['hashes'] = hashes
6969

7070
if component.get_license():
71-
licenses: list[dict[str, dict[str, str]]] = [
71+
licenses: List[Dict[str, Dict[str, str]]] = [
7272
{
7373
"license": {
7474
"name": str(component.get_license())
@@ -81,9 +81,9 @@ def _get_component_as_dict(self, component: Component) -> object:
8181
c['author'] = str(component.get_author())
8282

8383
if self.component_supports_external_references() and component.get_external_references():
84-
ext_references: list[dict[str, Union[str, list[dict[str, str]]]]] = []
84+
ext_references: List[Dict[str, Union[str, List[Dict[str, str]]]]] = []
8585
for ext_ref in component.get_external_references():
86-
ref: dict[str, Union[str, list[dict[str, str]]]] = {
86+
ref: Dict[str, Union[str, List[Dict[str, str]]]] = {
8787
"type": ext_ref.get_reference_type().value,
8888
"url": ext_ref.get_url()
8989
}
@@ -92,7 +92,7 @@ def _get_component_as_dict(self, component: Component) -> object:
9292
ref['comment'] = str(ext_ref.get_comment())
9393

9494
if ext_ref.get_hashes():
95-
ref_hashes: list[dict[str, str]] = []
95+
ref_hashes: List[Dict[str, str]] = []
9696
for ref_hash in ext_ref.get_hashes():
9797
ref_hashes.append({
9898
"alg": ref_hash.get_algorithm().value,
@@ -107,21 +107,21 @@ def _get_component_as_dict(self, component: Component) -> object:
107107

108108
def _get_metadata_as_dict(self) -> object:
109109
bom_metadata = self.get_bom().get_metadata()
110-
metadata: dict[str, Union[str, list[dict[str, Union[str, list[dict[str, str]]]]]]] = {
110+
metadata: Dict[str, Union[str, List[Dict[str, Union[str, List[Dict[str, str]]]]]]] = {
111111
"timestamp": bom_metadata.get_timestamp().isoformat()
112112
}
113113

114114
if self.bom_metadata_supports_tools():
115-
tools: list[dict[str, Union[str, list[dict[str, str]]]]] = []
115+
tools: List[Dict[str, Union[str, List[Dict[str, str]]]]] = []
116116
for tool in bom_metadata.get_tools():
117-
tool_dict: dict[str, Union[str, list[dict[str, str]]]] = {
117+
tool_dict: Dict[str, Union[str, List[Dict[str, str]]]] = {
118118
"vendor": tool.get_vendor(),
119119
"name": tool.get_name(),
120120
"version": tool.get_version()
121121
}
122122

123123
if len(tool.get_hashes()) > 0:
124-
hashes: list[dict[str, str]] = []
124+
hashes: List[Dict[str, str]] = []
125125
for tool_hash in tool.get_hashes():
126126
hashes.append({
127127
"alg": tool_hash.get_algorithm().value,

cyclonedx/parser/environment.py

+12-16
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,14 @@
3131
import sys
3232
from pkg_resources import DistInfoDistribution # type: ignore
3333

34-
if sys.version_info >= (3, 8, 0):
34+
if sys.version_info >= (3, 8):
3535
from importlib.metadata import metadata
36-
import email
36+
from email.message import Message as _MetadataReturn
3737
else:
38-
from importlib_metadata import metadata # type: ignore
39-
import email
38+
from importlib_metadata import metadata
39+
from importlib_metadata._meta import PackageMetadata as _MetadataReturn
4040

4141
from . import BaseParser
42-
4342
from ..model.component import Component
4443

4544

@@ -60,22 +59,19 @@ def __init__(self) -> None:
6059
c = Component(name=i.project_name, version=i.version)
6160

6261
i_metadata = self._get_metadata_for_package(i.project_name)
63-
if 'Author' in i_metadata.keys():
64-
c.set_author(author=i_metadata.get('Author'))
62+
if 'Author' in i_metadata:
63+
c.set_author(author=i_metadata['Author'])
6564

66-
if 'License' in i_metadata.keys() and i_metadata.get('License') != 'UNKNOWN':
67-
c.set_license(license_str=i_metadata.get('License'))
65+
if 'License' in i_metadata and i_metadata['License'] != 'UNKNOWN':
66+
c.set_license(license_str=i_metadata['License'])
6867

69-
if 'Classifier' in i_metadata.keys():
70-
for classifier in i_metadata.get_all('Classifier'):
68+
if 'Classifier' in i_metadata:
69+
for classifier in i_metadata['Classifier']:
7170
if str(classifier).startswith('License :: OSI Approved :: '):
7271
c.set_license(license_str=str(classifier).replace('License :: OSI Approved :: ', '').strip())
7372

7473
self._components.append(c)
7574

7675
@staticmethod
77-
def _get_metadata_for_package(package_name: str) -> email.message.Message:
78-
if sys.version_info >= (3, 8, 0):
79-
return metadata(package_name)
80-
else:
81-
return metadata(package_name)
76+
def _get_metadata_for_package(package_name: str) -> _MetadataReturn:
77+
return metadata(package_name)

cyclonedx/utils/conda.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from json import JSONDecodeError
2222
from typing import Optional
2323

24-
if sys.version_info >= (3, 8, 0):
24+
if sys.version_info >= (3, 8):
2525
from typing import TypedDict
2626
else:
2727
from typing_extensions import TypedDict

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ keywords = [
4141
"Bug Tracker" = "https://github.com/CycloneDX/cyclonedx-python-lib/issues"
4242

4343
[tool.poetry.dependencies]
44+
# keep `requirements.lowest.txt` file in sync
4445
python = "^3.6"
4546
packageurl-python = "^0.9.4"
4647
requirements_parser = "^0.2.0"

requirements.lowest.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# exactly pinned dependencies to the lowest version regardless of python_version
2+
# see pyptoject file for ranges
3+
4+
packageurl-python == 0.9.4
5+
requirements_parser == 0.2.0
6+
setuptools == 50.3.2
7+
importlib-metadata == 4.8.1 # ; python_version < '3.8'
8+
toml == 0.10.2
9+
typing-extensions == 3.10.0 # ; python_version < '3.8'
10+
types-setuptools == 57.4.2
11+
types-toml == 0.10.1

tests/test_parser_environment.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ def test_simple(self) -> None:
3838
# We can only be sure that tox is in the environment, for example as we use tox to run tests
3939
c_tox: Component = [x for x in parser.get_components() if x.get_name() == 'tox'][0]
4040
self.assertIsNotNone(c_tox.get_license())
41-
self.assertEqual('MIT License', c_tox.get_license())
41+
self.assertEqual('MIT', c_tox.get_license())

tox.ini

+11-7
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
minversion = 3.10
88
envlist =
99
flake8
10-
mypy
11-
py{310,39,38,37,36}
10+
mypy-{locked,lowest}
11+
py{310,39,38,37,36}-{locked,lowest}
1212
isolated_build = True
1313
skip_missing_interpreters = True
1414
usedevelop = False
@@ -18,17 +18,21 @@ download = False
1818
# settings in this category apply to all other testenv, if not overwritten
1919
skip_install = True
2020
whitelist_externals = poetry
21-
deps = poetry
21+
deps =
22+
poetry
2223
commands_pre =
2324
{envpython} --version
2425
poetry install -v
26+
lowest: poetry run pip install -U -r requirements.lowest.txt
27+
poetry run pip freeze
2528
commands =
26-
poetry run coverage run --source=cyclonedx -m unittest discover -s tests
29+
poetry run coverage run --source=cyclonedx -m unittest discover -s tests -v
2730

28-
[testenv:mypy]
31+
[testenv:mypy{,-locked,-lowest}]
2932
commands =
30-
poetry run mypy
31-
# mypy config is on own file: `.mypy.ini`
33+
# mypy config is in own file: `.mypy.ini`
34+
!lowest: poetry run mypy
35+
lowest: poetry run mypy --python-version=3.6
3236

3337
[testenv:flake8]
3438
commands =

0 commit comments

Comments
 (0)