ets-labs / python-dependency-injector

Dependency injection framework for Python
https://python-dependency-injector.ets-labs.org/
BSD 3-Clause "New" or "Revised" License
3.98k stars 306 forks source link

Attribute error on updater #451

Closed sjosegarcia closed 3 years ago

sjosegarcia commented 3 years ago

For some reason I am getting an attribute error. Here is the stack: dependency-injector = ">=4.32.0"

Traceback (most recent call last):
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/uvicorn/protocols/http/httptools_impl.py", line 398, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/fastapi/applications.py", line 199, in __call__
    await super().__call__(scope, receive, send)
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/starlette/applications.py", line 111, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc from None
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/starlette/middleware/cors.py", line 78, in __call__
    await self.app(scope, receive, send)
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/starlette/middleware/sessions.py", line 75, in __call__
    await self.app(scope, receive, send_wrapper)
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/starlette/routing.py", line 566, in __call__
    await route.handle(scope, receive, send)
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/starlette/routing.py", line 227, in handle
    await self.app(scope, receive, send)
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/starlette/routing.py", line 41, in app
    response = await func(request)
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/fastapi/routing.py", line 201, in app
    raw_response = await run_endpoint_function(
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/fastapi/routing.py", line 148, in run_endpoint_function
    return await dependant.call(**values)
  File "/root/.local/share/virtualenvs/bot-Z2emI11w/lib/python3.9/site-packages/dependency_injector/wiring.py", line 624, in _patched
    result = await fn(*args, **to_inject)
  File "/app/bot/endpoints/bot_endpoints.py", line 37, in bot_index
    bot_service.updater.dispatcher.process_update(
AttributeError: 'dependency_injector.providers.ThreadSafeSingleton' object has no attribute 'updater'

bot_service.py

from providers.token_service import TokenService
from containers.token_container import TokenContainer
from setup.config import get_settings
from schema.token_info import GenericTokenInfo
from telegram.ext import CommandHandler, MessageHandler, Filters
from telegram.ext.callbackcontext import CallbackContext
from telegram.update import Update
from telegram import ParseMode, Bot
from telegram.ext import Updater
from dependency_injector.wiring import inject, Provider
from schema.token_info import GenericTokenInfo, TokenInfo
from loguru import logger
from telegram.ext import Updater

class BotService:
    def __init__(self) -> None:
        bot = Bot(get_settings().telegram_bot_api_key)
        self.updater = Updater(bot=bot, workers=0)
        # self._process_handlers()
        self._generate_token_commands()
        if get_settings().debug:
            self.updater.start_polling()
            self.updater.idle()

    def set_webhook(self) -> bool:
        hooked = self.updater.bot.set_webhook(
            url=f"{get_settings().telegram_bot_webhook_url}/{get_settings().telegram_bot_api_key}",
        )
        return hooked

    def main_menu(self, update: Update, context: CallbackContext) -> None:
        update.message.reply_text("Here are a list of commands available for to you!")

    def tokens(self, update: Update, context: CallbackContext) -> None:
        update.message.reply_text(
            "GODZ tokens:\n\n\tGODZ - GodZilliqa DAO token.\n\n\tGDFI - GodZilliqa DeFi token."
        )

    def whitepaper(self, update: Update, context: CallbackContext) -> None:
        update.message.reply_text("Whitepaper can be found here:")

    def new_member(self, update: Update, context: CallbackContext) -> None:
        for member in update.message.new_chat_members:
            if not member.is_bot:
                update.message.reply_text("Welcome to GodZilliqa DeFi,")

    def unknown(self, update: Update, context: CallbackContext) -> None:
        context.bot.send_message(
            chat_id=update.effective_chat.id,
            text="Use /start to find a list of commands.",
        )

    @inject
    def _generate_token_commands(
        self,
        token_service: TokenService = Provider[TokenContainer.token_provider],
    ) -> None:
        logger.info(type(token_service))
        tokens = token_service.get_all_tokens()
        for token in tokens:
            price = token_service.get_token_info(token.symbol)
            self._register_token_command(token, price)

    def _register_token_command(
        self, token: GenericTokenInfo, price: TokenInfo
    ) -> None:
        token_website = (
            f"\n<a href='{token.website}'>Website</a>" if token.website else ""
        )
        token_whitepaper = (
            f"\n<a href='{token.whitepaper}'>Whitepaper</a>" if token.whitepaper else ""
        )
        token_address = (
            f"\n<a href='https://viewblock.io/zilliqa/address/{token.address_bech32}'>{token.symbol} contract on viewblock.io</a>"
            if token.address_bech32
            else ""
        )
        token_zilstream = f"\n<a href='https://zilstream.com/tokens/{token.symbol}'>View {token.symbol} on zilstream.com</a>"
        token_price = f"\n<b>{price.rate:.2f} ZIL - ${price.rate_usd:.2f}</b>"
        token_info = (
            f"<b>{token.name} ({token.symbol})\nScore: {token.viewblock_score}/100</b>"
        )

        self.updater.dispatcher.add_handler(
            CommandHandler(
                token.symbol.lower(),
                lambda update, context: update.message.reply_text(
                    f"{token_info}{token_price}{token_website}{token_whitepaper}{token_address}{token_zilstream}",
                    parse_mode=ParseMode.HTML,
                    disable_notification=True,
                    disable_web_page_preview=True,
                    allow_sending_without_reply=True,
                ),
            )
        )

    def _process_handlers(self) -> None:
        self.updater.dispatcher.add_handler(CommandHandler("menu", self.main_menu))
        self.updater.dispatcher.add_handler(CommandHandler("tokens", self.tokens))
        self.updater.dispatcher.add_handler(
            CommandHandler("whitepaper", self.whitepaper)
        )
        self.updater.dispatcher.add_handler(
            MessageHandler(Filters.status_update.new_chat_members, self.new_member)
        )
        self.updater.dispatcher.add_handler(
            MessageHandler(Filters.command, self.unknown)
        )

bot_container.py

from providers.bot_service import BotService
from dependency_injector.containers import DeclarativeContainer
from dependency_injector.providers import ThreadSafeSingleton

class BotContainer(DeclarativeContainer):
    bot_provider: BotService = ThreadSafeSingleton(BotService)

start_web.py

from containers.token_container import TokenContainer
from containers.bot_container import BotContainer
from providers import bot_service
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.sessions import SessionMiddleware
from endpoints.api_endpoints import api_router
from setup.config import get_settings
from loguru import logger
from endpoints import bot_endpoints

@logger.catch
def setup_app() -> FastAPI:
    settings = get_settings()
    token_container = TokenContainer()
    token_container.wire(modules=[bot_service])
    bot_container = BotContainer()
    bot_container.wire(modules=[bot_endpoints])
    app = FastAPI(
        debug=settings.debug,
        title=settings.project_name,
        openapi_url=f"{settings.api_v1_str}/openapi.json",
        default_response_class=ORJSONResponse,
    )
    app.token_container = token_container
    app.bot_container = bot_container

    app.include_router(api_router)
    app.add_middleware(SessionMiddleware, secret_key=settings.api_secret_key)
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    @app.on_event("startup")
    async def startup() -> None:
        pass

    @app.on_event("shutdown")
    async def shutdown() -> None:
        pass

    return app

bot_endpoints.py

from providers.bot_service import BotService
from containers.bot_container import BotContainer
from fastapi import APIRouter
from dependency_injector.wiring import inject, Provider
from telegram import Update
from fastapi.requests import Request
from setup.config import get_settings
from fastapi.responses import ORJSONResponse
from fastapi import Depends

telegram_bot_router = APIRouter()

@telegram_bot_router.get(f"/{get_settings().telegram_bot_api_key}/setwebhook")
@inject
async def bot_webhook(
    bot_service: BotService = Depends(Provider[BotContainer.bot_provider]),
) -> ORJSONResponse:
    return ORJSONResponse({"hooked": bot_service.set_webhook()})

@telegram_bot_router.get(f"/{get_settings().telegram_bot_api_key}/webhookinfo")
@inject
async def bot_webhook_info(
    bot_service: BotService = Depends(Provider[BotContainer.bot_provider]),
) -> ORJSONResponse:
    return ORJSONResponse(bot_service.dispatcher.bot.get_webhook_info().to_dict())

@telegram_bot_router.post(f"/{get_settings().telegram_bot_api_key}")
@inject
async def bot_index(
    request: Request,
    bot_service: BotService = Depends(Provider[BotContainer.bot_provider]),
) -> ORJSONResponse:
    data = await request.json()
    bot_service.updater.dispatcher.process_update(
        Update.de_json(data, bot_service.updater.dispatcher.bot)
    )
    return ORJSONResponse({})
rmk135 commented 3 years ago

Hi @sjosegarcia ,

This error occurs because you use Provider wiring marker. It injects provider, but not an instance provided by provider. To fix the problem use Provide marker:

    bot_service: BotService = Depends(Provide[BotContainer.bot_provider]),
rmk135 commented 3 years ago

Closing the issue. Feel free to comment or open a new issue if you need any other help.

sjosegarcia commented 3 years ago

Sorry about the late response, but now I ran across another issue. I was wondering what might be causing this?

AttributeError: 'Provide' object has no attribute 'set_webhook'

bot_container.py

from providers.bot_repository import BotRepository
from providers.token_service import TokenService
from providers.ban_service import BanService
from providers.bot_service import BotService
from providers.cas_service import CasService
from providers.chat_id_service import ChatIdService
from providers.link_removal_service import LinkRemovalService
from providers.welcome_message_service import WelcomeMessageService
from dependency_injector.containers import DeclarativeContainer
from dependency_injector.providers import ThreadSafeSingleton, Singleton

class BotContainer(DeclarativeContainer):
    bot_repository = ThreadSafeSingleton(BotRepository)
    bot_service = Singleton(BotService, bot_repository=bot_repository)
    token_service = Singleton(TokenService, bot_repository=bot_repository)
    ban_service = Singleton(BanService, bot_repository=bot_repository)
    cas_service = Singleton(CasService, bot_repository=bot_repository)
    chatid_service = Singleton(ChatIdService, bot_repository=bot_repository)
    link_removal_service = Singleton(LinkRemovalService, bot_repository=bot_repository)
    welcome_message_service = Singleton(
        WelcomeMessageService, bot_repository=bot_repository
    )

bot_endpoints.py

from fastapi import APIRouter
from telegram import Update
from fastapi.requests import Request
from setup.config import get_settings
from fastapi.responses import ORJSONResponse
from dependency_injector.wiring import inject, Provide
from fastapi import Depends
from containers.bot_container import BotContainer, BotService, BotRepository

telegram_bot_router = APIRouter()

@telegram_bot_router.get(f"/{get_settings().telegram_bot_api_key}/setwebhook")
@inject
async def bot_webhook(
    bot_service: BotService = Depends(Provide[BotContainer.bot_service]),
) -> ORJSONResponse:
    return ORJSONResponse({"hooked": bot_service.set_webhook()})

@telegram_bot_router.get(f"/{get_settings().telegram_bot_api_key}/webhookinfo")
@inject
async def bot_webhook_info(
    bot_repository: BotRepository = Depends(Provide[BotContainer.bot_repository]),
) -> ORJSONResponse:
    return ORJSONResponse(bot_repository.updater.bot.get_webhook_info().to_dict())

@telegram_bot_router.post(f"/{get_settings().telegram_bot_api_key}")
@inject
async def bot_index(
    request: Request,
    bot_repository: BotRepository = Depends(Provide[BotContainer.bot_repository]),
) -> None:
    data = await request.json()
    bot_repository.updater.dispatcher.process_update(
        Update.de_json(data, bot_repository.updater.dispatcher.bot)
    )
sjosegarcia commented 3 years ago

I fixed the issue above, and I am in a full circle. It now displays the same error with what I opened with.

AttributeError: 'dependency_injector.providers.ThreadSafeSingleton' object has no attribute 'updater'
at bot_index (/app/bot/endpoints/bot_endpoints.py:37)

BotRepository

from setup.config import get_settings
from telegram.ext import Updater
from telegram import Bot

class BotRepository:
    def __init__(self) -> None:
        bot = Bot(get_settings().telegram_bot_api_key)
        self.updater = Updater(bot=bot, workers=1)
sjosegarcia commented 3 years ago

I got it working. I am closing the issue. I needed to add the config into the container.