Skip to content

Add note in error message about trying to fetch data type that is more concrete or abstract than currently is provided #425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Rud356 opened this issue Apr 8, 2025 · 1 comment · Fixed by #431
Assignees
Labels
enhancement New feature or request
Milestone

Comments

@Rud356
Copy link

Rud356 commented Apr 8, 2025

Here's example code with error that was unclear:
sqla_provider.py

from dishka import Provider, Scope, provide
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase
from typing import Protocol, runtime_checkable, Self
from contextlib import AbstractAsyncContextManager

@runtime_checkable
class TransactionManager(Protocol, AbstractAsyncContextManager):
    ...

class Base(DeclarativeBase):
    """
    Основа классов таблиц SQLAlchemy.
    """

class TransactionManagerSQLA(TransactionManager):
    def __init__(self, **kwargs):
        ...


@runtime_checkable
class Repository(Protocol):
    transaction: TransactionManager


class RepositorySQLA(Repository):
    def __init__(self, transaction: TransactionManagerSQLA):
        self.transaction: TransactionManagerSQLA = transaction

    @staticmethod
    async def init_db(engine: AsyncEngine) -> None:
        """
        Инициализирует БД.

        :type engine: Подключение к базе данных.
        :return: Ничего.
        """
        async with engine.begin() as conn:
            await conn.run_sync(Base.metadata.create_all)

    @staticmethod
    async def drop_db(engine: AsyncEngine) -> None:
        """
        Удаляет все данные из БД.

        :type engine: Подключение к базе данных.
        :return: Ничего.
        """
        async with engine.begin() as conn:
            await conn.run_sync(Base.metadata.drop_all)

class SQLAlchemyProvider(Provider):
    def __init__(self, engine: AsyncEngine):
        super().__init__()
        self.engine: AsyncEngine = engine
        self.session_maker: async_sessionmaker[
            AsyncSession
        ] = async_sessionmaker(
            self.engine,
            expire_on_commit=False
        )

    @provide(scope=Scope.REQUEST)
    def get_session(self) -> AsyncSession:
        """
        Получает сессию SQLAlchemy.

        :return: Сессия SQLAlchemy.
        """
        return self.session_maker()

    @provide(scope=Scope.REQUEST)
    def get_transaction_manager(self, session: AsyncSession) -> TransactionManagerSQLA:
        """
        Получает менеджер транзакций.

        :param session: Асинхронная сессия SQLAlchemy.
        :return: Менеджер транзакции.
        """

        if (transaction_init := session.get_transaction()) is not None:
            return TransactionManagerSQLA(session, transaction_init)

        else:
            return TransactionManagerSQLA(session, session.begin())

    @provide(scope=Scope.REQUEST)
    def get_repository(self) -> Repository:
        """
        Получает репозиторий SQLAlchemy.

        :return: SQLAlchemy версия репозитория.
        """
        return RepositorySQLA(
            self.get_transaction_manager(
                self.get_session()
            )
        )

# Tests fixtures code
import pytest
import pytest_asyncio  # noqa: used as plugin
from dishka import Container, make_container
from sqlalchemy.ext.asyncio import create_async_engine


@pytest.fixture(scope="session")
def engine():
    return create_async_engine("sqlite+aiosqlite:///:memory:")

@pytest.fixture(scope="session")
def provider(engine) -> SQLAlchemyProvider:
    return SQLAlchemyProvider(engine)

@pytest.fixture()
def container(provider: SQLAlchemyProvider) -> Container:
    container: Container = make_container(provider)
    return container

@pytest.fixture()
def repo(container: Container) -> RepositorySQLA:
    with container() as request_container:
        repo: RepositorySQLA = request_container.get(RepositorySQLA)
    return repo

def test(repo):
    return True

Currently returned error is reported like this:

self = <dishka.container.Container object at 0x00000217149CE260>
key = DependencyKey(type_hint=<class 'server.data_storage.sql_implementation.repository_sqla.RepositorySQLA'>, component='')

    def _get_unlocked(self, key: DependencyKey) -> Any:
        if key in self._cache:
            return self._cache[key]
        compiled = self.registry.get_compiled(key)
        if not compiled:
            if not self.parent_container:
>               raise NoFactoryError(key)
E               dishka.exceptions.NoFactoryError: Cannot find factory for (RepositorySQLA, component='', scope=Scope.REQUEST). Check scopes in your providers. It is missing or has invalid scope.

..\venv\Lib\site-packages\dishka\container.py:182: NoFactoryError

Desired output: message with note about which data types can be provided that are in same hierarchy can be provided, and suggest using those

@Tishka17 Tishka17 added the enhancement New feature or request label Apr 8, 2025
@ApostolFet
Copy link
Contributor

Can I take this issue to work?

@Tishka17 Tishka17 moved this to Backlog in Dishka kanban Apr 13, 2025
@Tishka17 Tishka17 removed this from Dishka kanban Apr 13, 2025
@Tishka17 Tishka17 moved this to Backlog in Dishka kanban Apr 13, 2025
@Tishka17 Tishka17 moved this from Backlog to In progress in Dishka kanban Apr 13, 2025
@Tishka17 Tishka17 added this to the 1.6 milestone Apr 24, 2025
@github-project-automation github-project-automation bot moved this from In progress to To be released in Dishka kanban May 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: To be released
Development

Successfully merging a pull request may close this issue.

3 participants