Пример #1
0
def test_api_router_decorated_function(fdef):
    """Test sync and async function definition, with an arg and a kwarg."""
    example = textwrap.dedent(
        f'''
        from fastapi import APIRouter

        from app.models import SomeModel
        from app.services import some_function
        from app.types import CustomType

        some_router = APIRouter(prefix='/some-path')

        {fdef} list_something(resource_id: CustomType, some_model: SomeModel = Depends(some_function)):
            return None
        '''
    )
    assert _get_error(example, error_code_filter='TC001,TC002,TC003', type_checking_fastapi_enabled=True) == {
        '4:0 ' + TC002.format(module='app.models.SomeModel'),
        '6:0 ' + TC002.format(module='app.types.CustomType'),
    }
    assert (
        _get_error(
            example, error_code_filter='TC001,TC002,TC003', type_checking_fastapi_dependency_support_enabled=True
        )
        == set()
    )
Пример #2
0
def test_complex_attrs_model_as_import(imp, dec, expected):
    """
    Test `attrs` classes together with a non-`attrs` class tha has a class var of another type.

    `attrs` classes are instantiated using different dataclass
    decorators which are imported as submodules using an alias.
    """
    example = textwrap.dedent(
        f'''
        {imp}
        from decimals import Decimal
        from decimal import Context

        {dec}
        class X:
            x: Decimal

        {dec}
        class Y:
            x: Decimal

        class Z:
            x: Context
        '''
    )
    assert _get_error(example, error_code_filter='TC001,TC002,TC003') == expected
Пример #3
0
def test_api_router_decorated_nested_function(fdef):
    example = textwrap.dedent(f'''
        import logging

        from typing import TYPE_CHECKING

        from fastapi import APIRouter, Request

        if TYPE_CHECKING:
            from starlette.responses import RedirectResponse

        logger = logging.getLogger(__name__)


        def get_auth_router() -> APIRouter:
            router = APIRouter(tags=['Auth'], include_in_schema=False)

            @router.get('/login')
            {fdef} login(request: Request) -> "RedirectResponse":
                ...

        ''')
    assert _get_error(example,
                      error_code_filter='TC001,TC002,TC003',
                      **defaults) == set()
Пример #4
0
    def test_literal(self):
        example = textwrap.dedent("""
        from __future__ import annotations

        x: Literal['string']
        """)
        assert _get_error(example) == set()
Пример #5
0
 def test_mixed_errors(self):
     example = textwrap.dedent(f"""
     import {mod}
     import pytest
     from x import y
     """)
     assert _get_error(example) == {
         '2:0 ' + TC001.format(module=f'{mod}'),
         '3:0 ' + TC002.format(module='pytest'),
         '4:0 ' + TC002.format(module='x.y'),
     }
Пример #6
0
    def test_callable_import(self):
        """__all__ declarations originally generated false positives."""
        example = textwrap.dedent("""
        from x import y

        class X:
            def __init__(self):
                self.all_sellable_models: list[CostModel] = y(
                    country=self.country
                )
        """)
        assert _get_error(example) == set()
Пример #7
0
    def test_all_tuple_declaration(self):
        """__all__ declarations originally generated false positives."""
        example = textwrap.dedent("""
        from app.models import SomeModel
        from another_app.models import AnotherModel

        __all__ = (
            'SomeModel',
            'AnotherModel'
        )
        """)
        assert _get_error(example) == set()
Пример #8
0
def test_app_decorated_function(fdef):
    example = textwrap.dedent(f'''
        from app.main import app
        from app.models import SomeModel
        from app.types import CustomType

        @app.get('/{{resource_id}}')
        {fdef} list_something(resource_id: CustomType, some_model: SomeModel = Depends(lambda: 1)):
            return None
        ''')
    assert _get_error(example,
                      error_code_filter='TC001,TC002,TC003',
                      **defaults) == set()
Пример #9
0
def test_complex_pydantic_model():
    """Test actual Pydantic models, with different annotation types."""
    example = textwrap.dedent('''
        from __future__ import annotations

        from datetime import datetime
        from pandas import DataFrame
        from typing import TYPE_CHECKING

        from pydantic import BaseModel, condecimal, validator

        if TYPE_CHECKING:
            from datetime import date
            from typing import Union


        def format_datetime(value: Union[str, datetime]) -> datetime:
            if isinstance(value, str):
                value = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f%z')
            assert isinstance(value, datetime)
            return value


        class ModelBase(BaseModel):
            id: int
            created_at: datetime
            updated_at: datetime

            _format_datetime = validator('created_at', 'updated_at', pre=True, allow_reuse=True)(format_datetime)


        class NestedModel(ModelBase):
            z: DataFrame
            x: int
            y: str


        class FinalModel(ModelBase):
            a: str
            b: int
            c: float
            d: bool
            e: date
            f: NestedModel
            g: condecimal(ge=Decimal(0)) = Decimal(0)

        ''')
    assert _get_error(example,
                      error_code_filter='TC002',
                      type_checking_pydantic_enabled=True) == set()
Пример #10
0
def test_non_pydantic_model(enabled, expected):
    """
    A class cannot be a pydantic model if it doesn't have a base class,
    so we should raise the same error here in both cases.
    """
    example = textwrap.dedent('''
        from pandas import DataFrame

        class X:
            x: DataFrame
        ''')
    assert _get_error(example,
                      error_code_filter='TC002',
                      type_checking_pydantic_enabled=enabled) == expected
Пример #11
0
    def test_import_not_flagged_when_existing_import_present(self):
        """
        When an import is made to a module, we treat it as already used.

        Guarding additional imports to the same module shouldn't present
        a performance gain of note, so it's probably not worth the effort.
        """
        example = """
            from os import x  # <-- would otherwise be flagged
            from os import y  # <-- but this should prevent that

            z = y
            """
        assert _get_error(textwrap.dedent(example)) == set()
Пример #12
0
    def test_conditional_import(self):
        example = textwrap.dedent("""
        version = 2

        if version == 2:
            import x
        else:
            import y as x

        var: x
        """)
        assert _get_error(example) == {
            "7:4 TC002 Move third-party import 'x' into a type-checking block"
        }
Пример #13
0
def test_class_with_base_class():
    """
    Whenever a class inherits from anything, we need
    to assume it might be a pydantic model, for which
    we need to register annotations as uses.
    """
    example = textwrap.dedent('''
        from pandas import DataFrame

        class X(Y):
            x: DataFrame
        ''')
    assert _get_error(example,
                      error_code_filter='TC002',
                      type_checking_pydantic_enabled=True) == set()
Пример #14
0
    def test_model_declarations_dont_trigger_error(self):
        """
        Initially found false positives in Django project, because name
        visitor did not capture the SomeModel usage in the example below.
        """
        example = textwrap.dedent("""
        from django.db import models
        from app.models import SomeModel

        class LoanProvider(models.Model):
            fk: SomeModel = models.ForeignKey(
                SomeModel,
                on_delete=models.CASCADE,
            )
        """)
        assert _get_error(example) == set()
Пример #15
0
def test_api_router_decorated_function(fdef):
    """Test sync and async function definition, with an arg and a kwarg."""
    example = textwrap.dedent(f'''
        from fastapi import APIRouter

        from app.models import SomeModel
        from app.services import some_function
        from app.types import CustomType

        some_router = APIRouter(prefix='/some-path')

        @some_router.get('/{{resource_id}}')
        {fdef} list_something(resource_id: CustomType, some_model: SomeModel = Depends(some_function)):
            return None
        ''')
    assert _get_error(example,
                      error_code_filter='TC001,TC002,TC003',
                      **defaults) == set()
Пример #16
0
    def test_type_checking_block_imports_dont_generate_errors(self):
        example = textwrap.dedent("""
        import x
        from y import z

        if TYPE_CHECKING:
            import a

            # arbitrary whitespace

            from b import c

        def test():
            pass
        """)
        assert _get_error(example) == {
            '2:0 ' + TC002.format(module='x'),
            '3:0 ' + TC002.format(module='y.z'),
        }
Пример #17
0
    def test_type_checking_block_formats_detected(self):
        """
        We should detect type-checking blocks, no matter* the format.

        https://github.com/snok/flake8-type-checking/issues/68
        """
        type_checking = """
            from typing import TYPE_CHECKING

            if TYPE_CHECKING:
                from pathlib import Path

            p: Path
            """
        typing_type_checking = """
            import typing

            if typing.TYPE_CHECKING:
                from pathlib import Path

            p: Path
            """
        alias = """
            from typing import TYPE_CHECKING as test

            if test:
                from pathlib import Path

            p: Path
            """
        aliased_typing = """
            import typing as T

            if T.TYPE_CHECKING:
                from pathlib import Path

            p: Path
            """
        for example in [
                type_checking, typing_type_checking, alias, aliased_typing
        ]:
            assert _get_error(textwrap.dedent(example)) == set()
def test_exempt_modules_option():
    """
    The plugin provides an option called `--type-checking-exempt-modules`
    which is meant to passlist certain modules from TC001 and TC002 errors.
    """
    # Check that typing is passlisted when exempted
    example = textwrap.dedent('''
        from typing import TYPE_CHECKING
        from pandas import DataFrame

        x: DataFrame
        ''')
    assert _get_error(example, error_code_filter='TC002') == {
        '3:0 ' + TC002.format(module='pandas.DataFrame')
    }
    assert _get_error(example,
                      error_code_filter='TC002',
                      type_checking_exempt_modules=['pandas']) == set()

    # Check that other basic errors are still caught
    example2 = textwrap.dedent('''
        from typing import TYPE_CHECKING
        from pandas import DataFrame
        from a import B

        x: Callable[[], List]
        ''')
    assert _get_error(example2, error_code_filter='TC002') == {
        '3:0 ' + TC002.format(module='pandas.DataFrame'),
        '4:0 ' + TC002.format(module='a.B'),
    }
    assert _get_error(example2,
                      error_code_filter='TC002',
                      type_checking_exempt_modules=['pandas']) == {
                          '4:0 ' + TC002.format(module='a.B')
                      }

    # Check Import
    example3 = textwrap.dedent('''
        import pandas

        x: pandas.DataFrame
        ''')
    assert _get_error(example3, error_code_filter='TC002') == {
        '2:0 ' + TC002.format(module='pandas')
    }
    assert _get_error(example3,
                      error_code_filter='TC002',
                      type_checking_exempt_modules=['pandas']) == set()
Пример #19
0
def test_type_checking_pydantic_enabled_baseclass_passlist(c):
    """Test that named tuples are not ignored."""
    example = textwrap.dedent(f'''
        from typing import {c}
        from x import Y, Z

        class ModelBase({c}):
            a: Y[str]
            b: Z[int]
        ''')
    assert _get_error(
        example,
        error_code_filter='TC002',
        type_checking_pydantic_enabled=True,
        type_checking_pydantic_enabled_baseclass_passlist=[
            'NamedTuple', 'TypedDict'
        ],
    ) == {
        '3:0 ' + TC002.format(module='x.Y'),
        '3:0 ' + TC002.format(module='x.Z'),
    }
Пример #20
0
def test_api_router_decorated_function_return_type(fdef):
    """
    We don't care about return types. To my knowledge,
    these are not evaluated by FastAPI/pydantic.
    """
    example = textwrap.dedent(f'''
        from fastapi import APIRouter
        from fastapi import Request

        from app.types import CustomType

        some_router = APIRouter(prefix='/some-path')

        @some_router.get('/{{resource_id}}')
        {fdef} list_something(request: Request) -> CustomType:
            return None
        ''')
    assert _get_error(example,
                      error_code_filter='TC001,TC002,TC003',
                      **defaults) == {
                          '5:0 ' + TC002.format(module='app.types.CustomType')
                      }
Пример #21
0
def test_attrs_model(imp, dec):
    """
    Test `attrs` classes together with a non-`attrs` class that has a class var of the same type.
    `attrs` classes are instantiated using different dataclass decorators. The `attrs` module is imported as whole.
    """
    example = textwrap.dedent(
        f'''
        {imp}
        from decimal import Decimal

        {dec}
        class X:
            x: Decimal

        {dec}
        class Y:
            x: Decimal

        class Z:
            x: Decimal
        '''
    )
    assert _get_error(example, error_code_filter='TC001,TC002,TC003') == set()
Пример #22
0
 def test_ellipsis(self):
     example = textwrap.dedent("""
     x: Tuple[str, ...]
     """)
     assert _get_error(example) == set()
Пример #23
0
def test_TC001_errors(example: str, expected: set[str]) -> None:
    assert _get_error(example, error_code_filter='TC001') == expected
Пример #24
0
def test_TC100_errors(example, expected):
    assert _get_error(example, error_code_filter='TC100') == expected