Giter Site home page Giter Site logo

Comments (13)

BasileBerckmoes avatar BasileBerckmoes commented on June 7, 2024 1

Great work @rampaged!

from botbuilder-python.

InfinytRam avatar InfinytRam commented on June 7, 2024

Thanks @BasileBerckmoes, I can look into this.

from botbuilder-python.

BasileBerckmoes avatar BasileBerckmoes commented on June 7, 2024

@rampaged thank you! I was exploring the code myself to see if i could contribute but i need to progress my understanding of the framework first

from botbuilder-python.

InfinytRam avatar InfinytRam commented on June 7, 2024

Hi @BasileBerckmoes,

I'm able to reproduce this issue in Teams. I used sample 58.teams-start-thread-in-channel to test this issue.

I added this piece of code to reproduce the issue in on_message_activity method:

        team_details = await TeamsInfo.get_team_details(turn_context)

        if team_details:
            team_id = team_details.id
            # Retrieve all members of the team
            team_members = await TeamsInfo.get_team_members(turn_context, team_id)
            for member in team_members:
                if member.id != turn_context.activity.recipient.id:  # Avoid messaging the bot itself
                    # Prepare the message
                    await turn_context.send_activity(
                        "some message here"
                    )

Attached a bot that demonstrates the problem. Here are the steps I followed to verify it:
58.teams-thread.zip

  1. Unzip the attached bot and follow this readme to set up and configure the bot locally
  2. Sideload the bot in Teams and add it to a Teams team in a channel
  3. start a new post and @ mention the bot (TeamsStartThreadInChannel)
    Screenshot from 2024-01-19 19-47-09
  4. Notice the error:
 [on_turn_error] unhandled error: 'CloudAdapter' object has no attribute 'create_connector_client'
Traceback (most recent call last):
  File "/home/rampage/Documents/bots/BotBuilder-Samples/archive/samples/python/58.teams-start-thread-in-channel/venv/lib/python3.10/site-packages/botbuild
er/core/bot_adapter.py", line 174, in run_pipeline
    return await self._middleware.receive_activity_with_status(
  File "/home/rampage/Documents/bots/BotBuilder-Samples/archive/samples/python/58.teams-start-thread-in-channel/venv/lib/python3.10/site-packages/botbuild
er/core/middleware_set.py", line 69, in receive_activity_with_status
    return await self.receive_activity_internal(context, callback)
  File "/home/rampage/Documents/bots/BotBuilder-Samples/archive/samples/python/58.teams-start-thread-in-channel/venv/lib/python3.10/site-packages/botbuild
er/core/middleware_set.py", line 79, in receive_activity_internal
    return await callback(context)
  File "/home/rampage/Documents/bots/BotBuilder-Samples/archive/samples/python/58.teams-start-thread-in-channel/venv/lib/python3.10/site-packages/botbuild
er/core/activity_handler.py", line 70, in on_turn
    await self.on_message_activity(turn_context)
  File "/home/rampage/Documents/bots/BotBuilder-Samples/archive/samples/python/58.teams-start-thread-in-channel/bots/teams_start_thread_in_channel.py", li
ne 17, in on_message_activity
    team_details = await TeamsInfo.get_team_details(turn_context)
  File "/home/rampage/Documents/bots/BotBuilder-Samples/archive/samples/python/58.teams-start-thread-in-channel/venv/lib/python3.10/site-packages/botbuild
er/core/teams/teams_info.py", line 120, in get_team_details
    teams_connector = await TeamsInfo.get_teams_connector_client(turn_context)
  File "/home/rampage/Documents/bots/BotBuilder-Samples/archive/samples/python/58.teams-start-thread-in-channel/venv/lib/python3.10/site-packages/botbuild
er/core/teams/teams_info.py", line 305, in get_teams_connector_client
    connector_client = await TeamsInfo._get_connector_client(turn_context)
  File "/home/rampage/Documents/bots/BotBuilder-Samples/archive/samples/python/58.teams-start-thread-in-channel/venv/lib/python3.10/site-packages/botbuild
er/core/teams/teams_info.py", line 321, in _get_connector_client
    return await turn_context.adapter.create_connector_client(
AttributeError: 'CloudAdapter' object has no attribute 'create_connector_client'

Call stack diagram:

teams_start_thread_in_channel.py
|__ on_message_activity (line 17)
    |
    |__ TeamsInfo.get_team_details
        |
        |__ TeamsInfo.get_teams_connector_client 
            |
            |__ TeamsInfo._get_connector_client (line 320)
                |
                |__ turn_context.adapter.create_connector_client (line 321) <---- error occurs

from botbuilder-python.

InfinytRam avatar InfinytRam commented on June 7, 2024

CC @stevkan to handle this issue internally with Microsoft engineers.

from botbuilder-python.

InfinytRam avatar InfinytRam commented on June 7, 2024

@BasileBerckmoes,

I'm in the same boat, teaching myself the framework as well.

I explored the python bot framework sdk source code and tweaked cloud_adapter.py and teams_info.py, which I think resolved the issue. However, I'm still uncertain if this is the right approach since I'm learning the framework and need feedback from the engineers.

Observation:

In the javascript sdk side, the implementation detail for getConnectorClient in teamsInfo.ts is as follows:

    private static getConnectorClient(context: TurnContext): ConnectorClient {
        const client =
            context.adapter && 'createConnectorClient' in context.adapter
                ? (context.adapter as BotFrameworkAdapter).createConnectorClient(context.activity.serviceUrl)
                : context.turnState?.get<ConnectorClient>(context.adapter.ConnectorClientKey);
        if (!client) {
            throw new Error('This method requires a connector client.');
        }

        return client;
    }

as opposed to _get_connector_client method in teams_info.py on the python sdk:

    @staticmethod
    async def _get_connector_client(turn_context: TurnContext) -> ConnectorClient:
        return await turn_context.adapter.create_connector_client(
            turn_context.activity.service_url
        )

In JavaScript, getConnectorClient checks if the "createConnectorClient" method exists in "context.adapter" and calls it, otherwise it retrieves a "ConnectorClient" from "context.turnState" using a predefined key.

So my idea was to implement the same logic as getConnectorClient method but in python sdk.

Changes:

updated _get_connector_client method in teams_info.py:

+    from botbuilder.integration.aiohttp import CloudAdapter

    async def _get_connector_client(turn_context: TurnContext) -> ConnectorClient:
+        if isinstance(turn_context.adapter, CloudAdapter):
+           return await turn_context.adapter.create_connector_client(turn_context)
+        else:
            return await turn_context.adapter.create_connector_client(
                turn_context.activity.service_url
            )

and in CloudAdapter class, I added this method:

    async def create_connector_client(self, turn_context: TurnContext):
        connector_client: ConnectorClient = turn_context.turn_state.get(
            self.BOT_CONNECTOR_CLIENT_KEY
        )

        return connector_client

finally, in 58.teams-start-thread-in-channel bot sample(not sdk), in file teams_start_thread_in_channel.py, i changed:

connector_client = await turn_context.adapter.create_connector_client(turn_context.activity.service_url)

to:

connector_client = await turn_context.adapter.create_connector_client(turn_context)

Result:

        # Get the team details
        team_details = await TeamsInfo.get_team_details(turn_context)

        if team_details:
            team_id = team_details.id
            # Retrieve all members of the team
            team_members = await TeamsInfo.get_team_members(turn_context, team_id)
            for member in team_members:
                if member.id != turn_context.activity.recipient.id:  # Avoid messaging the bot itself
                    # Prepare the message
                    await turn_context.send_activity(
                        f"name {member.name}"
                    )

Screenshot from 2024-01-20 02-03-52

from botbuilder-python.

tracyboehrer avatar tracyboehrer commented on June 7, 2024

@rampaged Hi Ram. Not sure why JS does what it does. But I avoid type checking ('isinstance(turn_context.adapter, CloudAdapter)) unless I absolutely need to. The JS version is just check if the 'createConnectorClient' method is available (no idea why, could be historical).

It would be typical for the stack to store the Connector in the turnState, and I wonder if we also don't have to check that out. That is, it's created and stored for that turn to avoid repeatedly creating new ones.

I'll compare to DotNet. My guess is something was missed when porting the CloudAdapter.

from botbuilder-python.

tracyboehrer avatar tracyboehrer commented on June 7, 2024

@rampaged Check out the impl of TeamsInfo in DotNet. It has methods for GetConnectorClient (which just gets from TurnState), and GetTeamsConnectorClient (which creates a new TeamConnectorClient).

ConnectorClient would be added to TurnState in difference places for BotFrameworkAdapter vs CloudAdapter. DotNet will also remove it from TurnState in specific places.

from botbuilder-python.

InfinytRam avatar InfinytRam commented on June 7, 2024

Thanks for the feedback @tracyboehrer. Looking into it.

from botbuilder-python.

InfinytRam avatar InfinytRam commented on June 7, 2024

Thanks for the pointers @tracyboehrer.

I made an update to the _get_connector_client method to retrieve the ConnectorClient directly from turn_context.turn_state.

Now it uses hasattr to check if the create_connector_client method exists, which seems to keep things compatible with different adapters without needing type checking.

    @staticmethod
    async def _get_connector_client(turn_context: TurnContext) -> ConnectorClient:
        if hasattr(turn_context.adapter, 'create_connector_client'):
            return await turn_context.adapter.create_connector_client(turn_context.activity.service_url)

        connector_client = turn_context.turn_state.get(
            BotAdapter.BOT_CONNECTOR_CLIENT_KEY
        )

        if connector_client is None:
            raise ValueError('This method requires a connector client.')

        return connector_client

Then for bot sample 58.teams-start-thread-in-channel, in file teams_start_thread_in_channel.py, i changed:

# works only with BotFrameworkAdapter
connector_client = await turn_context.adapter.create_connector_client(turn_context.activity.service_url)

to

# works for both CloudAdapter and BotFrameworkAdapter
connector_client = await TeamsInfo._get_connector_client(turn_context)

This change seems to maintain compatibility with both CloudAdapter and BotFrameworkAdapter.

from botbuilder-python.

InfinytRam avatar InfinytRam commented on June 7, 2024

I've updated the PR #2062. Let me know your thoughts.

from botbuilder-python.

BasileBerckmoes avatar BasileBerckmoes commented on June 7, 2024

@rampaged is there any way i can ask you some questions? The teams samples are not ported yet to the CloudAdaptor and im having a few difficulties. Should i put my questions here or can we chat somewhere else?

from botbuilder-python.

InfinytRam avatar InfinytRam commented on June 7, 2024

Hey @BasileBerckmoes, Of course. Happy to help.

Note that for "how-to" questions, the Microsoft Bot Framework team  prefers  posting questions on
Stack Overflow with the #botframework tag.

The official Bot Framework Github repos are the preferred platform for submitting bug fixes and feature requests.

from botbuilder-python.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.