Beispiel #1
0
def test_invalid_req(rule_runner: RuleRunner) -> None:
    """Test that we give a nice error message."""
    fake_file_tgt = PythonRequirementsFile({"sources": ["doesnt matter"]}, Address("doesnt_matter"))
    with pytest.raises(ExecutionError) as exc:
        assert_python_requirements(
            rule_runner,
            "python_requirements()",
            "\n\nNot A Valid Req == 3.7",
            expected_file_dep=fake_file_tgt,
            expected_targets=[],
        )
    assert "Invalid requirement 'Not A Valid Req == 3.7' in requirements.txt at line 3" in str(
        exc.value
    )

    # Give a nice error message if it looks like they're using pip VCS-style requirements.
    with pytest.raises(ExecutionError) as exc:
        assert_python_requirements(
            rule_runner,
            "python_requirements()",
            "git+https://github.com/pypa/pip.git#egg=pip",
            expected_file_dep=fake_file_tgt,
            expected_targets=[],
        )
    assert "It looks like you're trying to use a pip VCS-style requirement?" in str(exc.value)
def test_source_override(rule_runner: RuleRunner) -> None:
    assert_poetry_requirements(
        rule_runner,
        "poetry_requirements(source='subdir/pyproject.toml')",
        dedent("""\
            [tool.poetry.dependencies]
            ansicolors = ">=1.18.0"
            [tool.poetry.dev-dependencies]
            """),
        pyproject_toml_relpath="subdir/pyproject.toml",
        expected_file_dep=PythonRequirementsFile(
            {"sources": ["subdir/pyproject.toml"]},
            address=Address("", target_name="subdir_pyproject.toml"),
        ),
        expected_targets=[
            PythonRequirementTarget(
                {
                    "dependencies": [":subdir_pyproject.toml"],
                    "requirements":
                    [PipRequirement.parse("ansicolors>=1.18.0")],
                },
                address=Address("", target_name="ansicolors"),
            ),
        ],
    )
Beispiel #3
0
def test_supply_python_requirements_file(rule_runner: RuleRunner) -> None:
    """This tests that we can supply our own `_python_requirements_file`."""
    assert_pipenv_requirements(
        rule_runner,
        dedent(
            """
            pipenv_requirements(
                requirements_relpath='custom/pipfile/Pipfile.lock',
                pipfile_target='//:custom_pipfile_target'
            )

            _python_requirements_file(
                name='custom_pipfile_target',
                sources=['custom/pipfile/Pipfile.lock']
            )
            """
        ),
        {"default": {"ansicolors": {"version": ">=1.18.0"}}},
        expected_file_dep=PythonRequirementsFile(
            {"sources": ["custom/pipfile/Pipfile.lock"]},
            address=Address("", target_name="custom_pipfile_target"),
        ),
        expected_targets=[
            PythonRequirementLibrary(
                {
                    "requirements": [Requirement.parse("ansicolors>=1.18.0")],
                    "dependencies": ["//:custom_pipfile_target"],
                },
                address=Address("", target_name="ansicolors"),
            ),
        ],
        pipfile_lock_relpath="custom/pipfile/Pipfile.lock",
    )
Beispiel #4
0
def test_requirements_txt(rule_runner: RuleRunner) -> None:
    """This tests that we correctly create a new python_requirement_library for each entry in a
    requirements.txt file.

    Some edge cases:
    * We ignore comments and options (values that start with `--`).
    * If a module_mapping is given, and the project is in the map, we copy over a subset of the
      mapping to the created target.
    * Projects get normalized thanks to Requirement.parse().
    """
    assert_python_requirements(
        rule_runner,
        "python_requirements(module_mapping={'ansicolors': ['colors']})",
        dedent(
            """\
            # Comment.
            --find-links=https://duckduckgo.com
            ansicolors>=1.18.0
            Django==3.2 ; python_version>'3'
            Un-Normalized-PROJECT  # Inline comment.
            pip@ git+https://github.com/pypa/pip.git
            """
        ),
        expected_file_dep=PythonRequirementsFile(
            {"sources": ["requirements.txt"]},
            address=Address("", target_name="requirements.txt"),
        ),
        expected_targets=[
            PythonRequirementLibrary(
                {
                    "dependencies": [":requirements.txt"],
                    "requirements": [Requirement.parse("ansicolors>=1.18.0")],
                    "module_mapping": {"ansicolors": ["colors"]},
                },
                address=Address("", target_name="ansicolors"),
            ),
            PythonRequirementLibrary(
                {
                    "dependencies": [":requirements.txt"],
                    "requirements": [Requirement.parse("Django==3.2 ; python_version>'3'")],
                },
                address=Address("", target_name="Django"),
            ),
            PythonRequirementLibrary(
                {
                    "dependencies": [":requirements.txt"],
                    "requirements": [Requirement.parse("Un_Normalized_PROJECT")],
                },
                address=Address("", target_name="Un-Normalized-PROJECT"),
            ),
            PythonRequirementLibrary(
                {
                    "dependencies": [":requirements.txt"],
                    "requirements": [Requirement.parse("pip@ git+https://github.com/pypa/pip.git")],
                },
                address=Address("", target_name="pip"),
            ),
        ],
    )
Beispiel #5
0
 def test_invalid_req(self) -> None:
     """Test that we give a nice error message."""
     with pytest.raises(ExecutionError) as exc:
         self.assert_python_requirements(
             "python_requirements()",
             "\n\nNot A Valid Req == 3.7",
             expected_file_dep=PythonRequirementsFile({}, address=Address("doesnt_matter")),
             expected_targets=[],
         )
     assert (
         "Invalid requirement in requirements.txt at line 3 due to value 'Not A Valid Req == "
         "3.7'."
     ) in str(exc.value)
Beispiel #6
0
def test_pipfile_lock(rule_runner: RuleRunner) -> None:
    """This tests that we correctly create a new python_requirement_library for each entry in a
    Pipfile.lock file.

    Edge cases:
    * Develop and Default requirements are used
    * If a module_mapping is given, and the project is in the map, we copy over a subset of the
        mapping to the created target.
    """
    assert_pipenv_requirements(
        rule_runner,
        "pipenv_requirements(module_mapping={'ansicolors': ['colors']})",
        {
            "default": {
                "ansicolors": {
                    "version": ">=1.18.0"
                }
            },
            "develop": {
                "cachetools": {
                    "markers": "python_version ~= '3.5'",
                    "version": "==4.1.1"
                }
            },
        },
        expected_file_dep=PythonRequirementsFile(
            {"sources": ["Pipfile.lock"]},
            Address("", target_name="Pipfile.lock")),
        expected_targets=[
            PythonRequirementLibrary(
                {
                    "requirements": [Requirement.parse("ansicolors>=1.18.0")],
                    "dependencies": [":Pipfile.lock"],
                    "module_mapping": {
                        "ansicolors": ["colors"]
                    },
                },
                Address("", target_name="ansicolors"),
            ),
            PythonRequirementLibrary(
                {
                    "requirements": [
                        Requirement.parse(
                            "cachetools==4.1.1;python_version ~= '3.5'")
                    ],
                    "dependencies": [":Pipfile.lock"],
                },
                Address("", target_name="cachetools"),
            ),
        ],
    )
def test_no_tool_poetry(rule_runner: RuleRunner) -> None:
    with pytest.raises(ExecutionError) as exc:
        assert_poetry_requirements(
            rule_runner,
            "poetry_requirements()",
            """
            foo = 4
            """,
            expected_file_dep=PythonRequirementsFile(
                {"sources": ["pyproject.toml"]},
                address=Address("", target_name="pyproject.toml"),
            ),
            expected_targets=[],
        )
    assert "`tool.poetry` found in pyproject.toml" in str(exc.value)
def test_no_req_defined_warning(rule_runner: RuleRunner, caplog: Any) -> None:
    assert_poetry_requirements(
        rule_runner,
        "poetry_requirements()",
        """
        [tool.poetry.dependencies]
        [tool.poetry.dev-dependencies]
        """,
        expected_file_dep=PythonRequirementsFile(
            {"sources": ["pyproject.toml"]},
            address=Address("", target_name="pyproject.toml"),
        ),
        expected_targets=[],
    )
    assert "No requirements defined" in caplog.text
Beispiel #9
0
def test_multiple_versions(rule_runner: RuleRunner) -> None:
    """This tests that we correctly create a new python_requirement_library for each unique
    dependency name in a requirements.txt file, grouping duplicated dependency names to handle
    multiple requirement strings per PEP 508."""

    assert_python_requirements(
        rule_runner,
        "python_requirements()",
        dedent(
            """\
            Django>=3.2
            Django==3.2.7
            confusedmonkey==86
            repletewateringcan>=7
            """
        ),
        expected_file_dep=PythonRequirementsFile(
            {"sources": ["requirements.txt"]},
            Address("", target_name="requirements.txt"),
        ),
        expected_targets=[
            PythonRequirementLibrary(
                {
                    "dependencies": [":requirements.txt"],
                    "requirements": [
                        Requirement.parse("Django>=3.2"),
                        Requirement.parse("Django==3.2.7"),
                    ],
                },
                Address("", target_name="Django"),
            ),
            PythonRequirementLibrary(
                {
                    "dependencies": [":requirements.txt"],
                    "requirements": [Requirement.parse("confusedmonkey==86")],
                },
                Address("", target_name="confusedmonkey"),
            ),
            PythonRequirementLibrary(
                {
                    "dependencies": [":requirements.txt"],
                    "requirements": [Requirement.parse("repletewateringcan>=7")],
                },
                Address("", target_name="repletewateringcan"),
            ),
        ],
    )
def test_bad_req_type(rule_runner: RuleRunner) -> None:
    with pytest.raises(ExecutionError) as exc:
        assert_poetry_requirements(
            rule_runner,
            "poetry_requirements()",
            """
            [tool.poetry.dependencies]
            foo = 4
            [tool.poetry.dev-dependencies]
            """,
            expected_file_dep=PythonRequirementsFile(
                {"sources": ["pyproject.toml"]},
                address=Address("", target_name="pyproject.toml"),
            ),
            expected_targets=[],
        )
    assert "was of type int" in str(exc.value)
def test_non_pep440_error(rule_runner: RuleRunner, caplog: Any) -> None:
    with pytest.raises(ExecutionError) as exc:
        assert_poetry_requirements(
            rule_runner,
            "poetry_requirements()",
            """
            [tool.poetry.dependencies]
            foo = "~r62b"
            [tool.poetry.dev-dependencies]
            """,
            expected_file_dep=PythonRequirementsFile(
                {"sources": ["pyproject.toml"]},
                address=Address("", target_name="pyproject.toml"),
            ),
            expected_targets=[],
        )
    assert 'Failed to parse requirement foo = "~r62b" in pyproject.toml' in str(exc.value)
Beispiel #12
0
def test_properly_creates_extras_requirements(rule_runner: RuleRunner) -> None:
    """This tests the proper parsing of requirements installed with specified extras."""
    assert_pipenv_requirements(
        rule_runner,
        "pipenv_requirements()",
        {
            "default": {
                "ansicolors": {
                    "version": ">=1.18.0",
                    "extras": ["neon"]
                }
            },
            "develop": {
                "cachetools": {
                    "markers": "python_version ~= '3.5'",
                    "version": "==4.1.1",
                    "extras": ["ring", "mongo"],
                }
            },
        },
        expected_file_dep=PythonRequirementsFile(
            {"sources": ["Pipfile.lock"]},
            Address("", target_name="Pipfile.lock")),
        expected_targets=[
            PythonRequirementLibrary(
                {
                    "requirements":
                    [Requirement.parse("ansicolors[neon]>=1.18.0")],
                    "dependencies": [":Pipfile.lock"],
                },
                Address("", target_name="ansicolors"),
            ),
            PythonRequirementLibrary(
                {
                    "requirements": [
                        Requirement.parse(
                            "cachetools[ring,mongo]==4.1.1;python_version ~= '3.5'"
                        )
                    ],
                    "dependencies": [":Pipfile.lock"],
                },
                Address("", target_name="cachetools"),
            ),
        ],
    )
Beispiel #13
0
 def test_relpath_override(self) -> None:
     self.assert_python_requirements(
         "python_requirements(requirements_relpath='subdir/requirements.txt')",
         "ansicolors>=1.18.0",
         requirements_txt_relpath="subdir/requirements.txt",
         expected_file_dep=PythonRequirementsFile(
             {"sources": ["subdir/requirements.txt"]},
             address=Address("", target_name="subdir/requirements.txt"),
         ),
         expected_targets=[
             PythonRequirementLibrary(
                 {
                     "dependencies": [":subdir/requirements.txt"],
                     "requirements": [Requirement.parse("ansicolors>=1.18.0")],
                 },
                 address=Address("", target_name="ansicolors"),
             ),
         ],
     )
def test_source_override(rule_runner: RuleRunner) -> None:
    assert_python_requirements(
        rule_runner,
        "python_requirements(source='subdir/requirements.txt')",
        "ansicolors>=1.18.0",
        requirements_txt_relpath="subdir/requirements.txt",
        expected_file_dep=PythonRequirementsFile(
            {"sources": ["subdir/requirements.txt"]},
            Address("", target_name="subdir_requirements.txt"),
        ),
        expected_targets=[
            PythonRequirementTarget(
                {
                    "dependencies": [":subdir_requirements.txt"],
                    "requirements":
                    [PipRequirement.parse("ansicolors>=1.18.0")],
                },
                Address("", target_name="ansicolors"),
            ),
        ],
    )
def test_pyproject_toml(rule_runner: RuleRunner) -> None:
    """This tests that we correctly create a new python_requirement for each entry in a
    pyproject.toml file.

    Note that this just ensures proper targets are created; see prior tests for specific parsing
    edge cases.
    """
    assert_poetry_requirements(
        rule_runner,
        dedent("""\
            poetry_requirements(
                # module_mapping should work regardless of capitalization.
                module_mapping={'ansiCOLORS': ['colors']},
                type_stubs_module_mapping={'Django-types': ['django']},
            )
            """),
        dedent("""\
            [tool.poetry.dependencies]
            Django = {version = "3.2", python = "3"}
            Django-types = "2"
            Un-Normalized-PROJECT = "1.0.0"
            [tool.poetry.dev-dependencies]
            ansicolors = ">=1.18.0"
            """),
        expected_file_dep=PythonRequirementsFile(
            {"sources": ["pyproject.toml"]},
            address=Address("", target_name="pyproject.toml"),
        ),
        expected_targets=[
            PythonRequirementTarget(
                {
                    "dependencies": [":pyproject.toml"],
                    "requirements":
                    [PipRequirement.parse("ansicolors>=1.18.0")],
                    "modules": ["colors"],
                },
                address=Address("", target_name="ansicolors"),
            ),
            PythonRequirementTarget(
                {
                    "dependencies": [":pyproject.toml"],
                    "requirements": [
                        PipRequirement.parse(
                            "Django==3.2 ; python_version == '3'")
                    ],
                },
                address=Address("", target_name="Django"),
            ),
            PythonRequirementTarget(
                {
                    "dependencies": [":pyproject.toml"],
                    "requirements": [PipRequirement.parse("Django-types==2")],
                    "type_stub_modules": ["django"],
                },
                address=Address("", target_name="Django-types"),
            ),
            PythonRequirementTarget(
                {
                    "dependencies": [":pyproject.toml"],
                    "requirements":
                    [PipRequirement.parse("Un_Normalized_PROJECT == 1.0.0")],
                },
                address=Address("", target_name="Un-Normalized-PROJECT"),
            ),
        ],
    )
def test_requirements_txt(rule_runner: RuleRunner) -> None:
    """This tests that we correctly create a new python_requirement for each entry in a
    requirements.txt file, where each dependency is unique.

    Some edge cases:
    * We ignore comments and options (values that start with `--`).
    * If a module_mapping is given, and the project is in the map, we copy over a subset of the
      mapping to the created target. It works regardless of capitalization.
    * Projects get normalized thanks to Requirement.parse().
    """
    assert_python_requirements(
        rule_runner,
        dedent("""\
            python_requirements(
                module_mapping={'ansiCOLORS': ['colors']},
                type_stubs_module_mapping={'Django-types': ['django']},
            )
            """),
        dedent("""\
            # Comment.
            --find-links=https://duckduckgo.com
            ansicolors>=1.18.0
            Django==3.2 ; python_version>'3'
            Django-types
            Un-Normalized-PROJECT  # Inline comment.
            pip@ git+https://github.com/pypa/pip.git
            """),
        expected_file_dep=PythonRequirementsFile(
            {"sources": ["requirements.txt"]},
            Address("", target_name="requirements.txt"),
        ),
        expected_targets=[
            PythonRequirementTarget(
                {
                    "dependencies": [":requirements.txt"],
                    "requirements":
                    [PipRequirement.parse("ansicolors>=1.18.0")],
                    "modules": ["colors"],
                },
                Address("", target_name="ansicolors"),
            ),
            PythonRequirementTarget(
                {
                    "dependencies": [":requirements.txt"],
                    "requirements":
                    [PipRequirement.parse("Django==3.2 ; python_version>'3'")],
                },
                Address("", target_name="Django"),
            ),
            PythonRequirementTarget(
                {
                    "dependencies": [":requirements.txt"],
                    "requirements": [PipRequirement.parse("Django-types")],
                    "type_stub_modules": ["django"],
                },
                Address("", target_name="Django-types"),
            ),
            PythonRequirementTarget(
                {
                    "dependencies": [":requirements.txt"],
                    "requirements":
                    [PipRequirement.parse("Un_Normalized_PROJECT")],
                },
                Address("", target_name="Un-Normalized-PROJECT"),
            ),
            PythonRequirementTarget(
                {
                    "dependencies": [":requirements.txt"],
                    "requirements": [
                        PipRequirement.parse(
                            "pip@ git+https://github.com/pypa/pip.git")
                    ],
                },
                Address("", target_name="pip"),
            ),
        ],
    )