dev #12

Merged
k1nq merged 30 commits from dev into master 2025-12-01 12:03:22 +00:00
26 changed files with 50 additions and 71 deletions
Showing only changes of commit f234e60e65 - Show all commits

View File

@ -161,7 +161,6 @@ uvx pytest
- `uv run ruff check app tests` — основной линтер (PEP8, сортировка импортов, дополнительные правила).
- `uv run ruff format app tests` — автоформатирование (аналог black) для единообразного стиля.
- `uv run isort .` — отдельная сортировка импортов (профиль `black`).
- `uv run mypy app services tests` — статическая проверка типов (строгий режим + плагин pydantic).
В CI/PR рекомендуется запускать команды именно в этом порядке, чтобы быстрее находить проблемы.

View File

@ -9,11 +9,10 @@ from collections.abc import Awaitable, Callable
from typing import Any
import redis.asyncio as redis
from app.core.config import settings
from redis.asyncio.client import Redis
from redis.exceptions import RedisError
from app.core.config import settings
logger = logging.getLogger(__name__)

View File

@ -4,9 +4,8 @@ from __future__ import annotations
from collections.abc import AsyncGenerator
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from app.core.config import settings
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
engine = create_async_engine(settings.database_url, echo=settings.sqlalchemy_echo)
AsyncSessionMaker = async_sessionmaker(bind=engine, expire_on_commit=False)

View File

@ -4,10 +4,9 @@ from __future__ import annotations
import logging
from starlette.types import ASGIApp, Receive, Scope, Send
from app.core.cache import cache_manager
from app.core.config import settings
from starlette.types import ASGIApp, Receive, Scope, Send
logger = logging.getLogger(__name__)

View File

@ -7,9 +7,8 @@ from datetime import datetime, timedelta, timezone
from typing import Any
import jwt
from passlib.context import CryptContext # type: ignore
from app.core.config import settings
from passlib.context import CryptContext # type: ignore
class PasswordHasher:

View File

@ -7,10 +7,12 @@ from enum import StrEnum
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy import DateTime, Enum as SqlEnum, ForeignKey, Integer, func, text
from sqlalchemy import DateTime, ForeignKey, Integer, func, text
from sqlalchemy import Enum as SqlEnum
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.types import JSON as SA_JSON, TypeDecorator
from sqlalchemy.types import JSON as SA_JSON
from sqlalchemy.types import TypeDecorator
from app.models.base import Base, enum_values

View File

@ -7,7 +7,8 @@ from decimal import Decimal
from enum import StrEnum
from pydantic import BaseModel, ConfigDict
from sqlalchemy import DateTime, Enum as SqlEnum, ForeignKey, Integer, Numeric, String, func
from sqlalchemy import DateTime, ForeignKey, Integer, Numeric, String, func
from sqlalchemy import Enum as SqlEnum
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import Base, enum_values

View File

@ -6,7 +6,8 @@ from datetime import datetime
from enum import StrEnum
from pydantic import BaseModel, ConfigDict
from sqlalchemy import DateTime, Enum as SqlEnum, ForeignKey, Integer, UniqueConstraint, func
from sqlalchemy import DateTime, ForeignKey, Integer, UniqueConstraint, func
from sqlalchemy import Enum as SqlEnum
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import Base, enum_values

View File

@ -13,7 +13,11 @@ from app.models.task import Task, TaskCreate
from app.repositories.activity_repo import ActivityOrganizationMismatchError, ActivityRepository
from app.repositories.task_repo import (
TaskAccessError as RepoTaskAccessError,
)
from app.repositories.task_repo import (
TaskOrganizationMismatchError as RepoTaskOrganizationMismatchError,
)
from app.repositories.task_repo import (
TaskQueryParams,
TaskRepository,
)

View File

@ -25,14 +25,6 @@ dev = [
"aiosqlite>=0.20.0",
]
[tool.isort]
profile = "black"
line_length = 100
combine_as_imports = true
default_section = "THIRDPARTY"
known_first_party = ["app", "tests"]
skip_glob = ["migrations/*"]
[tool.mypy]
python_version = "3.14"
plugins = ["pydantic.mypy"]

View File

@ -6,13 +6,12 @@ from collections.abc import AsyncGenerator
import pytest
import pytest_asyncio
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from app.api.deps import get_cache_backend, get_db_session
from app.core.security import password_hasher
from app.main import create_app
from app.models import Base
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from tests.utils.fake_redis import InMemoryRedis

View File

@ -5,14 +5,13 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from app.core.security import jwt_service
from app.models.contact import Contact
from app.models.deal import Deal
from app.models.organization import Organization
from app.models.organization_member import OrganizationMember, OrganizationRole
from app.models.user import User
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
@dataclass(slots=True)

View File

@ -5,10 +5,9 @@ from __future__ import annotations
from datetime import datetime, timedelta, timezone
import pytest
from app.models.activity import Activity, ActivityType
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from app.models.activity import Activity, ActivityType
from tests.api.v1.task_activity_shared import auth_headers, make_token, prepare_scenario

View File

@ -7,15 +7,14 @@ from datetime import datetime, timedelta, timezone
from decimal import Decimal
import pytest
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from app.core.security import jwt_service
from app.models.contact import Contact
from app.models.deal import Deal, DealStage, DealStatus
from app.models.organization import Organization
from app.models.organization_member import OrganizationMember, OrganizationRole
from app.models.user import User
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
@dataclass(slots=True)

View File

@ -3,14 +3,13 @@
from __future__ import annotations
import pytest
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from app.core.security import password_hasher
from app.models.organization import Organization
from app.models.organization_member import OrganizationMember, OrganizationRole
from app.models.user import User
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
@pytest.mark.asyncio

View File

@ -3,13 +3,12 @@
from __future__ import annotations
import pytest
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from app.models.contact import Contact
from app.models.organization_member import OrganizationMember, OrganizationRole
from app.models.user import User
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from tests.api.v1.task_activity_shared import auth_headers, make_token, prepare_scenario

View File

@ -5,12 +5,11 @@ from __future__ import annotations
from decimal import Decimal
import pytest
from app.models.activity import Activity, ActivityType
from app.models.deal import Deal, DealStage, DealStatus
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from app.models.activity import Activity, ActivityType
from app.models.deal import Deal, DealStage, DealStatus
from tests.api.v1.task_activity_shared import auth_headers, make_token, prepare_scenario

View File

@ -8,11 +8,6 @@ from typing import cast
import pytest
import pytest_asyncio
from httpx import ASGITransport, AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.schema import Table
from app.api.deps import get_db_session
from app.core.security import jwt_service
from app.main import create_app
@ -20,6 +15,10 @@ from app.models import Base
from app.models.organization import Organization
from app.models.organization_member import OrganizationMember, OrganizationRole
from app.models.user import User
from httpx import ASGITransport, AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.schema import Table
@pytest_asyncio.fixture()

View File

@ -5,10 +5,9 @@ from __future__ import annotations
from datetime import datetime, timedelta, timezone
import pytest
from app.models.task import Task
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from app.models.task import Task
from tests.api.v1.task_activity_shared import (
auth_headers,
create_deal,

View File

@ -7,9 +7,6 @@ from collections.abc import AsyncGenerator
import pytest
import pytest_asyncio
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from app.models.activity import Activity, ActivityType
from app.models.base import Base
from app.models.contact import Contact
@ -25,6 +22,8 @@ from app.services.activity_service import (
ActivityValidationError,
)
from app.services.organization_service import OrganizationContext
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
@pytest_asyncio.fixture()

View File

@ -8,9 +8,6 @@ from decimal import Decimal
import pytest
import pytest_asyncio
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from app.models import Base
from app.models.contact import Contact
from app.models.deal import Deal, DealStage, DealStatus
@ -19,6 +16,8 @@ from app.models.organization_member import OrganizationMember, OrganizationRole
from app.models.user import User
from app.repositories.analytics_repo import AnalyticsRepository
from app.services.analytics_service import AnalyticsService, invalidate_analytics_cache
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from tests.utils.fake_redis import InMemoryRedis

View File

@ -6,12 +6,11 @@ from typing import cast
from unittest.mock import MagicMock
import pytest # type: ignore[import-not-found]
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.security import JWTService, PasswordHasher
from app.models.user import User
from app.repositories.user_repo import UserRepository
from app.services.auth_service import AuthService, InvalidCredentialsError, InvalidRefreshTokenError
from sqlalchemy.ext.asyncio import AsyncSession
class StubUserRepository(UserRepository):

View File

@ -7,10 +7,6 @@ from collections.abc import AsyncGenerator
import pytest
import pytest_asyncio
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from app.models.base import Base
from app.models.contact import Contact, ContactCreate
from app.models.deal import Deal
@ -26,6 +22,9 @@ from app.services.contact_service import (
ContactUpdateData,
)
from app.services.organization_service import OrganizationContext
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
@pytest_asyncio.fixture()

View File

@ -8,10 +8,6 @@ from decimal import Decimal
import pytest # type: ignore[import-not-found]
import pytest_asyncio # type: ignore[import-not-found]
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from app.models.activity import Activity, ActivityType
from app.models.base import Base
from app.models.contact import Contact
@ -29,6 +25,9 @@ from app.services.deal_service import (
DealUpdateData,
)
from app.services.organization_service import OrganizationContext
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
@pytest_asyncio.fixture()

View File

@ -6,8 +6,6 @@ from typing import cast
from unittest.mock import MagicMock
import pytest # type: ignore[import-not-found]
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.organization import Organization
from app.models.organization_member import OrganizationMember, OrganizationRole
from app.repositories.org_repo import OrganizationRepository
@ -19,6 +17,7 @@ from app.services.organization_service import (
OrganizationMemberAlreadyExistsError,
OrganizationService,
)
from sqlalchemy.ext.asyncio import AsyncSession
class StubOrganizationRepository(OrganizationRepository):

View File

@ -8,10 +8,6 @@ from datetime import datetime, timedelta, timezone
import pytest
import pytest_asyncio
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from app.models.activity import Activity, ActivityType
from app.models.base import Base
from app.models.contact import Contact
@ -29,6 +25,9 @@ from app.services.task_service import (
TaskService,
TaskUpdateData,
)
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
@pytest_asyncio.fixture()