From 9eedb4ff27bb81f4ad323e9fa0f79230b0710032 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 13 Dec 2022 18:22:29 +0100 Subject: [PATCH] feat: parsers can outbut more debug messages (#466) Signed-off-by: Jan Kowalleck --- .github/workflows/docker.yml | 2 + cyclonedx_py/client.py | 82 ++++++++++++++++++----------- cyclonedx_py/parser/_debug.py | 41 +++++++++++++++ cyclonedx_py/parser/conda.py | 20 ++++++- cyclonedx_py/parser/environment.py | 39 +++++++++----- cyclonedx_py/parser/pipenv.py | 28 ++++++++-- cyclonedx_py/parser/poetry.py | 38 +++++++++---- cyclonedx_py/parser/requirements.py | 52 +++++++++++------- poetry.lock | 2 +- pyproject.toml | 1 + 10 files changed, 226 insertions(+), 79 deletions(-) create mode 100644 cyclonedx_py/parser/_debug.py diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e2200696..cf8aa7c1 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -69,6 +69,7 @@ jobs: - name: Build own SBoM (XML) run: > docker run --rm "$DOCKER_TAG" + -X --environment --format=xml --output=- @@ -76,6 +77,7 @@ jobs: - name: Build own SBoM (JSON) run: > docker run --rm "$DOCKER_TAG" + -X --environment --format=json --output=- diff --git a/cyclonedx_py/client.py b/cyclonedx_py/client.py index ca461834..f731b2d5 100644 --- a/cyclonedx_py/client.py +++ b/cyclonedx_py/client.py @@ -23,7 +23,7 @@ import os import sys from datetime import datetime -from typing import Optional +from typing import Any, Optional from cyclonedx.model import Tool from cyclonedx.model.bom import Bom @@ -74,7 +74,7 @@ def __init__(self, args: argparse.Namespace) -> None: if self._arguments.debug_enabled: self._DEBUG_ENABLED = True self._debug_message('!!! DEBUG MODE ENABLED !!!') - self._debug_message('Parsed Arguments: {}'.format(self._arguments)) + self._debug_message('Parsed Arguments: {}', self._arguments) def _get_output_format(self) -> _CLI_OUTPUT_FORMAT: return _CLI_OUTPUT_FORMAT(str(self._arguments.output_format).lower()) @@ -82,11 +82,11 @@ def _get_output_format(self) -> _CLI_OUTPUT_FORMAT: def get_output(self) -> BaseOutput: try: parser = self._get_input_parser() - except CycloneDxCmdNoInputFileSupplied as e: - print(f'ERROR: {str(e)}', file=sys.stderr) + except CycloneDxCmdNoInputFileSupplied as error: + print(f'ERROR: {str(error)}', file=sys.stderr) exit(1) - except CycloneDxCmdException as e: - print(f'ERROR: {str(e)}', file=sys.stderr) + except CycloneDxCmdException as error: + print(f'ERROR: {str(error)}', file=sys.stderr) exit(1) if parser and parser.has_warnings(): @@ -134,13 +134,13 @@ def get_output(self) -> BaseOutput: def execute(self) -> None: output_format = self._get_output_format() - self._debug_message(f'output_format: {output_format}') + self._debug_message('output_format: {}', output_format) # Quick check for JSON && SchemaVersion <= 1.1 if output_format == OutputFormat.JSON and \ str(self._arguments.output_schema_version) in ['1.0', '1.1']: self._error_and_exit( - message='CycloneDX schema does not support JSON output in Schema Versions < 1.2', + 'CycloneDX schema does not support JSON output in Schema Versions < 1.2', exit_code=2 ) @@ -154,7 +154,7 @@ def execute(self) -> None: output_file = self._arguments.output_file output_filename = os.path.realpath( output_file if isinstance(output_file, str) else _output_default_filenames[output_format]) - self._debug_message('Will be outputting SBOM to file at: {}'.format(output_filename)) + self._debug_message('Will be outputting SBOM to file at: {}', output_filename) output.output_to_file(filename=output_filename, allow_overwrite=self._arguments.output_file_overwrite) @staticmethod @@ -240,18 +240,23 @@ def get_arg_parser(*, prog: Optional[str] = None) -> argparse.ArgumentParser: return arg_parser - def _debug_message(self, message: str) -> None: + def _debug_message(self, message: str, *args: Any, **kwargs: Any) -> None: if self._DEBUG_ENABLED: - print('[DEBUG] - {} - {}'.format(datetime.now(), message), file=sys.stderr) + print(f'[DEBUG] - {{__t}} - {message}'.format(*args, **kwargs, __t=datetime.now()), + file=sys.stderr) @staticmethod - def _error_and_exit(message: str, exit_code: int = 1) -> None: - print('[ERROR] - {} - {}'.format(datetime.now(), message), file=sys.stderr) + def _error_and_exit(message: str, *args: Any, exit_code: int = 1, **kwargs: Any) -> None: + print(f'[ERROR] - {{__t}} - {message}'.format(*args, **kwargs, __t=datetime.now()), + file=sys.stderr) exit(exit_code) def _get_input_parser(self) -> BaseParser: if self._arguments.input_from_environment: - return EnvironmentParser(use_purl_bom_ref=self._arguments.use_purl_bom_ref) + return EnvironmentParser( + use_purl_bom_ref=self._arguments.use_purl_bom_ref, + debug_message=lambda m, *a, **k: self._debug_message(f'EnvironmentParser {m}', *a, **k) + ) # All other Parsers will require some input - grab it now! if not self._arguments.input_source: @@ -259,11 +264,11 @@ def _get_input_parser(self) -> BaseParser: current_directory = os.getcwd() try: if self._arguments.input_from_conda_explicit: - raise CycloneDxCmdNoInputFileSupplied('When using input from Conda Explicit, you need to pipe input' - 'via STDIN') + raise CycloneDxCmdNoInputFileSupplied( + 'When using input from Conda Explicit, you need to pipe input via STDIN') elif self._arguments.input_from_conda_json: - raise CycloneDxCmdNoInputFileSupplied('When using input from Conda JSON, you need to pipe input' - 'via STDIN') + raise CycloneDxCmdNoInputFileSupplied( + 'When using input from Conda JSON, you need to pipe input via STDIN') elif self._arguments.input_from_pip: self._arguments.input_source = open(os.path.join(current_directory, 'Pipfile.lock'), 'r') elif self._arguments.input_from_poetry: @@ -272,10 +277,10 @@ def _get_input_parser(self) -> BaseParser: self._arguments.input_source = open(os.path.join(current_directory, 'requirements.txt'), 'r') else: raise CycloneDxCmdException('Parser type could not be determined.') - except FileNotFoundError as e: + except FileNotFoundError as error: raise CycloneDxCmdNoInputFileSupplied( - f'No input file was supplied and no input was provided on STDIN:\n{str(e)}' - ) + f'No input file was supplied and no input was provided on STDIN:\n{str(error)}' + ) from error input_data_fh = self._arguments.input_source with input_data_fh: @@ -283,20 +288,35 @@ def _get_input_parser(self) -> BaseParser: input_data_fh.close() if self._arguments.input_from_conda_explicit: - return CondaListExplicitParser(conda_data=input_data, - use_purl_bom_ref=self._arguments.use_purl_bom_ref) + return CondaListExplicitParser( + conda_data=input_data, + use_purl_bom_ref=self._arguments.use_purl_bom_ref, + debug_message=lambda m, *a, **k: self._debug_message(f'CondaListExplicitParser {m}', *a, **k) + ) elif self._arguments.input_from_conda_json: - return CondaListJsonParser(conda_data=input_data, - use_purl_bom_ref=self._arguments.use_purl_bom_ref) + return CondaListJsonParser( + conda_data=input_data, + use_purl_bom_ref=self._arguments.use_purl_bom_ref, + debug_message=lambda m, *a, **k: self._debug_message(f'CondaListJsonParser {m}', *a, **k) + ) elif self._arguments.input_from_pip: - return PipEnvParser(pipenv_contents=input_data, - use_purl_bom_ref=self._arguments.use_purl_bom_ref) + return PipEnvParser( + pipenv_contents=input_data, + use_purl_bom_ref=self._arguments.use_purl_bom_ref, + debug_message=lambda m, *a, **k: self._debug_message(f'PipEnvParser {m}', *a, **k) + ) elif self._arguments.input_from_poetry: - return PoetryParser(poetry_lock_contents=input_data, - use_purl_bom_ref=self._arguments.use_purl_bom_ref) + return PoetryParser( + poetry_lock_contents=input_data, + use_purl_bom_ref=self._arguments.use_purl_bom_ref, + debug_message=lambda m, *a, **k: self._debug_message(f'PoetryParser {m}', *a, **k) + ) elif self._arguments.input_from_requirements: - return RequirementsParser(requirements_content=input_data, - use_purl_bom_ref=self._arguments.use_purl_bom_ref) + return RequirementsParser( + requirements_content=input_data, + use_purl_bom_ref=self._arguments.use_purl_bom_ref, + debug_message=lambda m, *a, **k: self._debug_message(f'RequirementsParser {m}', *a, **k) + ) else: raise CycloneDxCmdException('Parser type could not be determined.') diff --git a/cyclonedx_py/parser/_debug.py b/cyclonedx_py/parser/_debug.py new file mode 100644 index 00000000..5e2e89de --- /dev/null +++ b/cyclonedx_py/parser/_debug.py @@ -0,0 +1,41 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +"""The following structures are internal helpers.""" + +from typing import TYPE_CHECKING, Any, Callable + +if TYPE_CHECKING: + from mypy_extensions import Arg, KwArg, VarArg + + DebugMessageCallback = Callable[[Arg(str, 'message'), VarArg(Any), KwArg(Any)], None] # noqa: F821 + """Callback for debug messaging. + + :parameter message: the format string. + :Other Parameters: the *args: to :func:`str.forma()`. + :Keyword Arguments: the **kwargs to :func:`str.format()`. + """ +else: + DebugMessageCallback = Callable[..., None] + + +def quiet(message: str, *_: Any, **__: Any) -> None: + """Do not print anything. + + Must be compatible to :py:data:`DebugMessageCallback`. + """ + pass diff --git a/cyclonedx_py/parser/conda.py b/cyclonedx_py/parser/conda.py index 16abd657..ed391909 100644 --- a/cyclonedx_py/parser/conda.py +++ b/cyclonedx_py/parser/conda.py @@ -31,14 +31,21 @@ parse_conda_json_to_conda_package, parse_conda_list_str_to_conda_package, ) +from ._debug import DebugMessageCallback, quiet class _BaseCondaParser(BaseParser, metaclass=ABCMeta): """Internal abstract parser - not for programmatic use. """ - def __init__(self, conda_data: str, use_purl_bom_ref: bool = False) -> None: + def __init__( + self, conda_data: str, use_purl_bom_ref: bool = False, + *, + debug_message: DebugMessageCallback = quiet + ) -> None: super().__init__() + debug_message('init {}', self.__class__.__name__) + self._debug_message = debug_message self._conda_packages: List[CondaPackage] = [] self._parse_to_conda_packages(data_str=conda_data) self._conda_packages_to_components(use_purl_bom_ref=use_purl_bom_ref) @@ -61,7 +68,9 @@ def _conda_packages_to_components(self, use_purl_bom_ref: bool) -> None: Converts the parsed `CondaPackage` instances into `Component` instances. """ + self._debug_message('processing conda_packages') for conda_package in self._conda_packages: + self._debug_message('processing conda_package: {!r}', conda_package) purl = conda_package_to_purl(conda_package) bom_ref = purl.to_string() if use_purl_bom_ref else None c = Component( @@ -89,11 +98,14 @@ class CondaListJsonParser(_BaseCondaParser): def _parse_to_conda_packages(self, data_str: str) -> None: conda_list_content = json.loads(data_str) - + self._debug_message('processing conda_list_content') for package in conda_list_content: + self._debug_message('processing package: {!r}', package) conda_package = parse_conda_json_to_conda_package(conda_json_str=json.dumps(package)) if conda_package: self._conda_packages.append(conda_package) + else: + self._debug_message('no conda_package -> skip') class CondaListExplicitParser(_BaseCondaParser): @@ -103,8 +115,12 @@ class CondaListExplicitParser(_BaseCondaParser): """ def _parse_to_conda_packages(self, data_str: str) -> None: + self._debug_message('processing data_str') for line in data_str.replace('\r\n', '\n').split('\n'): line = line.strip() + self._debug_message('processing line: {}', line) conda_package = parse_conda_list_str_to_conda_package(conda_list_str=line) if conda_package: self._conda_packages.append(conda_package) + else: + self._debug_message('no conda_package -> skip') diff --git a/cyclonedx_py/parser/environment.py b/cyclonedx_py/parser/environment.py index f7f42510..de74ee24 100644 --- a/cyclonedx_py/parser/environment.py +++ b/cyclonedx_py/parser/environment.py @@ -49,6 +49,8 @@ from cyclonedx.model.component import Component from cyclonedx.parser import BaseParser +from ._debug import DebugMessageCallback, quiet + class EnvironmentParser(BaseParser): """ @@ -57,45 +59,58 @@ class EnvironmentParser(BaseParser): Best used when you have virtual Python environments per project. """ - def __init__(self, use_purl_bom_ref: bool = False) -> None: + def __init__( + self, use_purl_bom_ref: bool = False, + *, + debug_message: DebugMessageCallback = quiet + ) -> None: super().__init__() + debug_message('init {}', self.__class__.__name__) + debug_message('late import pkg_resources') import pkg_resources + debug_message('processing pkg_resources.working_set') i: DistInfoDistribution for i in iter(pkg_resources.working_set): + debug_message('processing working_set item: {!r}', i) purl = PackageURL(type='pypi', name=i.project_name, version=i.version) bom_ref = purl.to_string() if use_purl_bom_ref else None c = Component(name=i.project_name, bom_ref=bom_ref, version=i.version, purl=purl) i_metadata = self._get_metadata_for_package(i.project_name) + debug_message('processing i_metadata') if 'Author' in i_metadata: c.author = i_metadata['Author'] - if 'License' in i_metadata and i_metadata['License'] and i_metadata['License'] != 'UNKNOWN': # Values might be ala `MIT` (SPDX id), `Apache-2.0 license` (arbitrary string), ... # Therefore, just go with a named license. try: c.licenses.add(LicenseChoice(license_=License(license_name=i_metadata['License']))) - except CycloneDxModelException: - # write a debug message? - pass + except CycloneDxModelException as error: + # @todo traceback and details to the output? + debug_message('Warning: suppressed {!r}', error) + del error + debug_message('processing classifiers') for classifier in i_metadata.get_all("Classifier", []): + debug_message('processing classifier: {!r}', classifier) + classifier = str(classifier) # Trove classifiers - https://packaging.python.org/specifications/core-metadata/#metadata-classifier # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers - if str(classifier).startswith('License :: OSI Approved :: '): - license_name = str(classifier).replace('License :: OSI Approved :: ', '').strip() - elif str(classifier).startswith('License :: '): - license_name = str(classifier).replace('License :: ', '').strip() + if classifier.startswith('License :: OSI Approved :: '): + license_name = classifier.replace('License :: OSI Approved :: ', '').strip() + elif classifier.startswith('License :: '): + license_name = classifier.replace('License :: ', '').strip() else: license_name = '' if license_name: try: c.licenses.add(LicenseChoice(license_=License(license_name=license_name))) - except CycloneDxModelException: - # write a debug message? - pass + except CycloneDxModelException as error: + # @todo traceback and details to the output? + debug_message('Warning: suppressed {!r}', error) + del error self._components.append(c) diff --git a/cyclonedx_py/parser/pipenv.py b/cyclonedx_py/parser/pipenv.py index 97eb0234..5c614c17 100644 --- a/cyclonedx_py/parser/pipenv.py +++ b/cyclonedx_py/parser/pipenv.py @@ -27,16 +27,26 @@ # See https://github.com/package-url/packageurl-python/issues/65 from packageurl import PackageURL # type: ignore +from ._debug import DebugMessageCallback, quiet + class PipEnvParser(BaseParser): - def __init__(self, pipenv_contents: str, use_purl_bom_ref: bool = False) -> None: + def __init__( + self, pipenv_contents: str, use_purl_bom_ref: bool = False, + *, + debug_message: DebugMessageCallback = quiet + ) -> None: super().__init__() + debug_message('init {}', self.__class__.__name__) + debug_message('loading pipenv_contents') pipfile_lock_contents = json.loads(pipenv_contents) pipfile_default: Dict[str, Dict[str, Any]] = pipfile_lock_contents.get('default') or {} + debug_message('processing pipfile_default') for (package_name, package_data) in pipfile_default.items(): + debug_message('processing package: {!r} {!r}', package_name, package_data) version = str(package_data.get('version') or 'unknown').lstrip('=') purl = PackageURL(type='pypi', name=package_name, version=version) bom_ref = purl.to_string() if use_purl_bom_ref else None @@ -44,6 +54,7 @@ def __init__(self, pipenv_contents: str, use_purl_bom_ref: bool = False) -> None if isinstance(package_data.get('hashes'), list): # Add download location with hashes stored in Pipfile.lock for pip_hash in package_data['hashes']: + debug_message('processing pip_hash: {!r}', pip_hash) ext_ref = ExternalReference( reference_type=ExternalReferenceType.DISTRIBUTION, url=XsUri(c.get_pypi_url()), @@ -51,12 +62,19 @@ def __init__(self, pipenv_contents: str, use_purl_bom_ref: bool = False) -> None ) ext_ref.hashes.add(HashType.from_composite_str(pip_hash)) c.external_references.add(ext_ref) - self._components.append(c) class PipEnvFileParser(PipEnvParser): - def __init__(self, pipenv_lock_filename: str, use_purl_bom_ref: bool = False) -> None: - with open(pipenv_lock_filename) as r: - super(PipEnvFileParser, self).__init__(pipenv_contents=r.read(), use_purl_bom_ref=use_purl_bom_ref) + def __init__( + self, pipenv_lock_filename: str, use_purl_bom_ref: bool = False, + *, + debug_message: DebugMessageCallback = quiet + ) -> None: + debug_message('open file: {}', pipenv_lock_filename) + with open(pipenv_lock_filename) as plf: + super(PipEnvFileParser, self).__init__( + pipenv_contents=plf.read(), use_purl_bom_ref=use_purl_bom_ref, + debug_message=debug_message + ) diff --git a/cyclonedx_py/parser/poetry.py b/cyclonedx_py/parser/poetry.py index bf161b20..b299d23c 100644 --- a/cyclonedx_py/parser/poetry.py +++ b/cyclonedx_py/parser/poetry.py @@ -17,7 +17,7 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. -from cyclonedx.exception.model import UnknownHashTypeException +from cyclonedx.exception.model import CycloneDxModelException from cyclonedx.model import ExternalReference, ExternalReferenceType, HashType, XsUri from cyclonedx.model.component import Component from cyclonedx.parser import BaseParser @@ -26,22 +26,33 @@ from packageurl import PackageURL # type: ignore from toml import loads as load_toml +from ._debug import DebugMessageCallback, quiet + class PoetryParser(BaseParser): - def __init__(self, poetry_lock_contents: str, use_purl_bom_ref: bool = False) -> None: + def __init__( + self, poetry_lock_contents: str, use_purl_bom_ref: bool = False, + *, + debug_message: DebugMessageCallback = quiet + ) -> None: super().__init__() + debug_message('init {}', self.__class__.__name__) + + debug_message('loading poetry_lock_contents') poetry_lock = load_toml(poetry_lock_contents) + debug_message('processing poetry_lock') for package in poetry_lock['package']: + debug_message('processing package: {!r}', package) purl = PackageURL(type='pypi', name=package['name'], version=package['version']) bom_ref = purl.to_string() if use_purl_bom_ref else None component = Component( name=package['name'], bom_ref=bom_ref, version=package['version'], purl=purl ) - for file_metadata in poetry_lock['metadata']['files'][package['name']]: + debug_message('processing file_metadata: {!r}', file_metadata) try: component.external_references.add(ExternalReference( reference_type=ExternalReferenceType.DISTRIBUTION, @@ -49,15 +60,24 @@ def __init__(self, poetry_lock_contents: str, use_purl_bom_ref: bool = False) -> comment=f'Distribution file: {file_metadata["file"]}', hashes=[HashType.from_composite_str(file_metadata['hash'])] )) - except UnknownHashTypeException: - # @todo add logging for this type of exception? - pass + except CycloneDxModelException as error: + # @todo traceback and details to the output? + debug_message('Warning: suppressed {!r}', error) + del error self._components.append(component) class PoetryFileParser(PoetryParser): - def __init__(self, poetry_lock_filename: str, use_purl_bom_ref: bool = False) -> None: - with open(poetry_lock_filename) as r: - super(PoetryFileParser, self).__init__(poetry_lock_contents=r.read(), use_purl_bom_ref=use_purl_bom_ref) + def __init__( + self, poetry_lock_filename: str, use_purl_bom_ref: bool = False, + *, + debug_message: DebugMessageCallback = quiet + ) -> None: + debug_message('open file: {}', poetry_lock_filename) + with open(poetry_lock_filename) as plf: + super(PoetryFileParser, self).__init__( + poetry_lock_contents=plf.read(), use_purl_bom_ref=use_purl_bom_ref, + debug_message=debug_message + ) diff --git a/cyclonedx_py/parser/requirements.py b/cyclonedx_py/parser/requirements.py index 0d334af2..251e0ed6 100644 --- a/cyclonedx_py/parser/requirements.py +++ b/cyclonedx_py/parser/requirements.py @@ -19,8 +19,7 @@ import os import os.path -from tempfile import NamedTemporaryFile, _TemporaryFileWrapper # Weak error -from typing import Any, Optional +from tempfile import NamedTemporaryFile # Weak error from cyclonedx.model import HashType from cyclonedx.model.component import Component @@ -30,30 +29,41 @@ from packageurl import PackageURL # type: ignore from pip_requirements_parser import RequirementsFile # type: ignore +from ._debug import DebugMessageCallback, quiet + class RequirementsParser(BaseParser): - def __init__(self, requirements_content: str, use_purl_bom_ref: bool = False) -> None: + def __init__( + self, requirements_content: str, use_purl_bom_ref: bool = False, + *, + debug_message: DebugMessageCallback = quiet + ) -> None: super().__init__() - parsed_rf: Optional[RequirementsFile] = None - requirements_file: Optional[_TemporaryFileWrapper[Any]] = None + debug_message('init {}', self.__class__.__name__) if os.path.exists(requirements_content): - parsed_rf = RequirementsFile.from_file( - requirements_content, include_nested=True) + debug_message('create RequirementsFile from file: {}', requirements_content) + parsed_rf = RequirementsFile.from_file(requirements_content, include_nested=True) else: - requirements_file = NamedTemporaryFile(mode='w+', delete=False) - requirements_file.write(requirements_content) - requirements_file.close() - - parsed_rf = RequirementsFile.from_file( - requirements_file.name, include_nested=False) + with NamedTemporaryFile(mode='w+', delete=False) as requirements_file: + debug_message('write requirements_content to TempFile: {}', requirements_file.name) + requirements_file.write(requirements_content) + try: + debug_message('create RequirementsFile from TempFile: {}', requirements_file.name) + parsed_rf = RequirementsFile.from_file(requirements_file.name, include_nested=False) + finally: + debug_message('unlink TempFile: {}', requirements_file.name) + os.unlink(requirements_file.name) + del requirements_file + debug_message('processing requirements') for requirement in parsed_rf.requirements: + debug_message('processing requirement: {!r}', requirement) name = requirement.link.url if requirement.is_local_path else requirement.name version = requirement.get_pinned_version or requirement.dumps_specifier() + debug_message('detected: {!r} {!r}', name, version) hashes = map(HashType.from_composite_str, requirement.hash_options) - if not version and not requirement.is_local_path: self._warnings.append( ParserWarning( @@ -73,11 +83,15 @@ def __init__(self, requirements_content: str, use_purl_bom_ref: bool = False) -> purl=purl )) - if requirements_file: - os.unlink(requirements_file.name) - class RequirementsFileParser(RequirementsParser): - def __init__(self, requirements_file: str, use_purl_bom_ref: bool = False) -> None: - super().__init__(requirements_content=requirements_file, use_purl_bom_ref=use_purl_bom_ref) + def __init__( + self, requirements_file: str, use_purl_bom_ref: bool = False, + *, + debug_message: DebugMessageCallback = quiet + ) -> None: + super().__init__( + requirements_content=requirements_file, use_purl_bom_ref=use_purl_bom_ref, + debug_message=debug_message + ) diff --git a/poetry.lock b/poetry.lock index cf0b5191..6867f5f4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -451,7 +451,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "659ed36c6acf03fff3a84d0030b52ee1597d8b1ab51e65eaeec36fb3662d7b1c" +content-hash = "8a8e2bb08219bd7ca43a369b055bbd2a4e3cf6641aac201293783b14df200a00" [metadata.files] attrs = [ diff --git a/pyproject.toml b/pyproject.toml index fce07510..d1196d88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ coverage = [ { python = ">=3.7", version = "^6.5", extras = ["toml"] }, ] mypy = "^0.971" +mypy-extensions = "^0.4.3" flake8 = "^4.0.1" flake8-annotations = {version = "^2.7.0", python = ">= 3.6.2"} flake8-bugbear = "^22.9.23"