Source code for cyclonedx_py.parser.requirements

# encoding: utf-8

# This file is part of CycloneDX Python Lib
#
# 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.

import os
import os.path
from tempfile import NamedTemporaryFile, _TemporaryFileWrapper  # Weak error
from typing import Any, Optional

from cyclonedx.model import HashType
from cyclonedx.model.component import Component
from cyclonedx.parser import BaseParser, ParserWarning

# See https://github.com/package-url/packageurl-python/issues/65
from packageurl import PackageURL  # type: ignore
from pip_requirements_parser import RequirementsFile  # type: ignore


[docs]class RequirementsParser(BaseParser): def __init__(self, requirements_content: str) -> None: super().__init__() parsed_rf: Optional[RequirementsFile] = None requirements_file: Optional[_TemporaryFileWrapper[Any]] = None if os.path.exists(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) for requirement in parsed_rf.requirements: name = requirement.link.url if requirement.is_local_path else requirement.name version = requirement.get_pinned_version or requirement.dumps_specifier() hashes = map(HashType.from_composite_str, requirement.hash_options) if not version and not requirement.is_local_path: self._warnings.append( ParserWarning( item=name, warning=(f"Requirement \'{name}\' does not have a pinned " "version and cannot be included in your CycloneDX SBOM.") ) ) else: self._components.append(Component( name=name, version=version, hashes=hashes, purl=PackageURL(type='pypi', name=name, version=version) )) if requirements_file: os.unlink(requirements_file.name)
[docs]class RequirementsFileParser(RequirementsParser): def __init__(self, requirements_file: str) -> None: super().__init__(requirements_content=requirements_file)