diff --git a/.github/workflows/linting.yaml b/.github/workflows/linting.yaml new file mode 100644 index 00000000..62a103df --- /dev/null +++ b/.github/workflows/linting.yaml @@ -0,0 +1,48 @@ +name: Linting +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + ruff: + name: Ruff Linter & Formatter + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up Python 3.9 + uses: actions/setup-python@v4 + with: + python-version: 3.9 + + - name: Install Poetry + run: pip install poetry + + - name: Cache poetry virtualenv + uses: actions/cache@v4 + with: + path: ~/.cache/pypoetry/virtualenvs + key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + + - name: Install dependencies + run: poetry install + + - name: Cache ruff + uses: actions/cache@v4 + with: + path: .ruff_cache + key: ${{ runner.os }}-ruff-${{ github.ref_name }}-${{ hashFiles('poetry.lock') }} + restore-keys: | + ${{ runner.os }}-ruff-main-${{ hashFiles('poetry.lock') }} + + - name: Run ruff formatter check + run: poetry run ruff format --check . + + - name: Run ruff linter + run: poetry run ruff check . diff --git a/.github/workflows/pylint.yaml b/.github/workflows/pylint.yaml deleted file mode 100644 index 62dcc3b3..00000000 --- a/.github/workflows/pylint.yaml +++ /dev/null @@ -1,56 +0,0 @@ -name: pylint - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - build: - name: pylint - runs-on: ubuntu-latest - - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Set up Python 3.9 - id: setup-python - uses: actions/setup-python@v4 - with: - python-version: 3.9 - - - name: Get pip cache dir - id: pip-cache - run: | - echo "::set-output name=dir::$(pip cache dir)" - - - name: Cache pip dependencies - uses: actions/cache@v4 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ steps.setup-python.outputs.python-version }} - - - name: Install Pylint - run: pip install pylint - - - name: Download previous run results - uses: dawidd6/action-download-artifact@8c96194455d424a24437ec57817e0aa1a1d72adb - continue-on-error: true - with: - workflow: pylint.yaml - workflow_conclusion: success - name: .pylint.d - path: /home/runner/.pylint.d - branch: "main" - - - name: Run Pylint - run: pylint symphony tests - - - name: Upload Pylint results - if: ${{ always() }} - uses: actions/upload-artifact@v4 - with: - name: .pylint.d - path: ~/.pylint.d diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..92b6fa24 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.8 + hooks: + - id: ruff-format + - id: ruff-check + args: [ --fix ] diff --git a/README.md b/README.md index 3809efb6..17d9ac76 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,23 @@ Symphony BDK for Python provides tools for building bots and integrating with Sy - Install dependencies: `poetry install` - Build the package: `poetry build` - Run tests: `poetry run pytest` -- Perform a pylint scan locally: `poetry run pylint ` +- Perform a lint scan locally: `poetry run ruff check .` +- Format code locally: `poetry run ruff format .` - Generate documentation locally: `cd docsrc && make html` +### Setting Up Git Hooks +This project uses `pre-commit` with `ruff` to automatically format and lint code. This is the recommended setup for contributors to ensure code style consistency. + +1. **Install development dependencies** (this will also install `pre--commit` and `ruff`): + ```bash + poetry install + ``` +2. **Install the git hooks**: + ```bash + poetry run pre-commit install + ``` +Now, `ruff` will automatically run on every commit, formatting your code and checking for linting errors. + ### Verification Verify the successful installation by running any of the following commands: ``` diff --git a/examples/activities/command_activity.py b/examples/activities/command_activity.py index 0e7d601a..ff23f5aa 100644 --- a/examples/activities/command_activity.py +++ b/examples/activities/command_activity.py @@ -26,7 +26,9 @@ async def on_activity(self, context: CommandContext): await self._messages.send_message(context.stream_id, "Hello, World!") -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) try: logging.info("Running activity example...") diff --git a/examples/activities/form_activity.py b/examples/activities/form_activity.py index 6c8f0ff4..b3900945 100644 --- a/examples/activities/form_activity.py +++ b/examples/activities/form_activity.py @@ -32,21 +32,27 @@ def __init__(self, messages: MessageService): self.messages = messages def matches(self, context: FormReplyContext) -> bool: - return context.form_id == "gif-category-form" \ - and context.get_form_value("action") == "submit" \ - and context.get_form_value("category") + return ( + context.form_id == "gif-category-form" + and context.get_form_value("action") == "submit" + and context.get_form_value("category") + ) async def on_activity(self, context: FormReplyContext): category = context.get_form_value("category") - await self.messages.send_message(context.source_event.stream.stream_id, - " You just submitted this category: " + category + "") + await self.messages.send_message( + context.source_event.stream.stream_id, + " You just submitted this category: " + category + "", + ) def load_gif_elements_form(): return (Path(__file__).parent.parent / "resources/gif.mml.xml").read_text(encoding="utf-8") -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) try: logging.info("Running activity example...") diff --git a/examples/activities/slash_command_activity.py b/examples/activities/slash_command_activity.py index d5eaa9ca..a8e04875 100644 --- a/examples/activities/slash_command_activity.py +++ b/examples/activities/slash_command_activity.py @@ -22,7 +22,9 @@ async def on_hello(context: CommandContext): await bdk.datafeed().start() -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) try: logging.info("Running activity example...") diff --git a/examples/activities/slash_command_with_arguments_activity.py b/examples/activities/slash_command_with_arguments_activity.py index 24a76314..3a78833d 100644 --- a/examples/activities/slash_command_with_arguments_activity.py +++ b/examples/activities/slash_command_with_arguments_activity.py @@ -16,7 +16,9 @@ async def run(): @activities.slash("/echo {@mention_argument}") async def on_echo_mention(context: CommandContext): mentioned_user = context.arguments.get_mention("mention_argument") - message = f"Mentioned user: {mentioned_user.user_display_name}, id: {mentioned_user.user_id}" + message = ( + f"Mentioned user: {mentioned_user.user_display_name}, id: {mentioned_user.user_id}" + ) await messages.send_message(context.stream_id, f"{message}") @@ -36,7 +38,6 @@ async def on_echo_cashtag(context: CommandContext): @activities.slash("/echo {first_string_argument} {second_string_argument}") async def on_echo_string_arguments(context: CommandContext): - # Get string argument with get_string first_string_argument = context.arguments.get_string("first_string_argument") @@ -52,11 +53,12 @@ async def on_echo_string_arguments(context: CommandContext): await bdk.datafeed().start() -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) try: logging.info("Running activity example...") asyncio.run(run()) except KeyboardInterrupt: logging.info("Ending activity example") - diff --git a/examples/activities/user_joined_room_activity.py b/examples/activities/user_joined_room_activity.py index ea846ce7..f24e5b0d 100644 --- a/examples/activities/user_joined_room_activity.py +++ b/examples/activities/user_joined_room_activity.py @@ -2,7 +2,10 @@ import logging.config from pathlib import Path -from symphony.bdk.core.activity.user_joined_room import UserJoinedRoomActivity, UserJoinedRoomContext +from symphony.bdk.core.activity.user_joined_room import ( + UserJoinedRoomActivity, + UserJoinedRoomContext, +) from symphony.bdk.core.config.loader import BdkConfigLoader from symphony.bdk.core.service.message.message_service import MessageService from symphony.bdk.core.symphony_bdk import SymphonyBdk @@ -23,14 +26,17 @@ def matches(self, context: UserJoinedRoomContext) -> bool: return True async def on_activity(self, context: UserJoinedRoomContext): - await self._messages.send_message(context.stream_id, - "Welcome to the room") + await self._messages.send_message( + context.stream_id, "Welcome to the room" + ) -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) try: logging.info("Running activity example...") asyncio.run(run()) except KeyboardInterrupt: - logging.info("Ending activity example") \ No newline at end of file + logging.info("Ending activity example") diff --git a/examples/authentication/auth.py b/examples/authentication/auth.py index dd66d5fc..cf66e53e 100644 --- a/examples/authentication/auth.py +++ b/examples/authentication/auth.py @@ -18,6 +18,8 @@ async def run(): logging.info(await obo_auth_session.session_token) -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) asyncio.run(run()) diff --git a/examples/authentication/ext_app_auth.py b/examples/authentication/ext_app_auth.py index d92d1258..6254b9d4 100644 --- a/examples/authentication/ext_app_auth.py +++ b/examples/authentication/ext_app_auth.py @@ -17,9 +17,13 @@ async def run(): # nosemgrep - please don't log secrets in production; this is just for demo purposes logging.debug("App token: %s, Symphony token: %s", ta, ts) # nosemgrep - logging.debug("Is token pair valid: %s", await ext_app_authenticator.is_token_pair_valid(ta, ts)) + logging.debug( + "Is token pair valid: %s", await ext_app_authenticator.is_token_pair_valid(ta, ts) + ) -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) asyncio.run(run()) diff --git a/examples/config.py b/examples/config.py index 31acd121..9d770aa6 100644 --- a/examples/config.py +++ b/examples/config.py @@ -1,6 +1,5 @@ from symphony.bdk.core.config.loader import BdkConfigLoader - config_1 = BdkConfigLoader.load_from_file("/absolute/path/to/config.yaml") with open("/absolute/path/to/config.yaml") as config_file: diff --git a/examples/datafeed/datafeed.py b/examples/datafeed/datafeed.py index d816e941..a9309b0a 100644 --- a/examples/datafeed/datafeed.py +++ b/examples/datafeed/datafeed.py @@ -19,13 +19,14 @@ async def run(): class RealTimeEventListenerImpl(RealTimeEventListener): - async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): # We do not recommend logging full events in production as it could expose sensitive data logging.debug("Received event: %s", event) -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) try: diff --git a/examples/datafeed/datafeed_stop_loop.py b/examples/datafeed/datafeed_stop_loop.py index 094949eb..46dbb891 100644 --- a/examples/datafeed/datafeed_stop_loop.py +++ b/examples/datafeed/datafeed_stop_loop.py @@ -25,7 +25,6 @@ async def run(): class RealTimeEventListenerImpl(RealTimeEventListener): - async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): logging.debug("Received event: %s", event.message.message_id) await asyncio.sleep(5) @@ -41,32 +40,34 @@ def filter(self, record): return True -logging.config.dictConfig({ - "version": 1, - "disable_existing_loggers": False, - "filters": {"contextFilter": {"()": "__main__.EventListenerLoggingFilter"}}, - "formatters": { - "standard": { - "format": "%(asctime)s - %(name)s - %(levelname)s - %(context_id)s - %(message)s" +logging.config.dictConfig( + { + "version": 1, + "disable_existing_loggers": False, + "filters": {"contextFilter": {"()": "__main__.EventListenerLoggingFilter"}}, + "formatters": { + "standard": { + "format": "%(asctime)s - %(name)s - %(levelname)s - %(context_id)s - %(message)s" + }, + }, + "handlers": { + "default": { + "level": "DEBUG", + "formatter": "standard", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", # Default is stderr + "filters": ["contextFilter"], + }, }, - }, - "handlers": { - "default": { - "level": "DEBUG", - "formatter": "standard", - "class": "logging.StreamHandler", - "stream": "ext://sys.stdout", # Default is stderr - "filters": ["contextFilter"] + "loggers": { + "": { # root logger + "handlers": ["default"], + "level": "DEBUG", + "propagate": False, + } }, - }, - "loggers": { - "": { # root logger - "handlers": ["default"], - "level": "DEBUG", - "propagate": False - } } -}) +) try: logging.info("Running datafeed example...") diff --git a/examples/datafeed/datahose.py b/examples/datafeed/datahose.py index 6a20a495..746cd9ca 100644 --- a/examples/datafeed/datahose.py +++ b/examples/datafeed/datahose.py @@ -6,9 +6,9 @@ from symphony.bdk.core.service.datafeed.real_time_event_listener import RealTimeEventListener from symphony.bdk.core.symphony_bdk import SymphonyBdk from symphony.bdk.gen.agent_model.v4_initiator import V4Initiator -from symphony.bdk.gen.agent_model.v4_room_updated import V4RoomUpdated -from symphony.bdk.gen.agent_model.v4_room_created import V4RoomCreated from symphony.bdk.gen.agent_model.v4_message_sent import V4MessageSent +from symphony.bdk.gen.agent_model.v4_room_created import V4RoomCreated +from symphony.bdk.gen.agent_model.v4_room_updated import V4RoomUpdated async def run(): @@ -21,7 +21,6 @@ async def run(): class RealTimeEventListenerImpl(RealTimeEventListener): - async def on_room_updated(self, initiator: V4Initiator, event: V4RoomUpdated): # We do not recommend logging full events in production as it could expose sensitive data logging.debug("Room updated: : %s", event.new_room_properties) @@ -35,7 +34,9 @@ async def on_room_created(self, initiator: V4Initiator, event: V4RoomCreated): logging.debug("Room created: %s", event.room_properties) -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) try: diff --git a/examples/extension/groups_extension.py b/examples/extension/groups_extension.py index e2fc6d89..9a36c5d9 100644 --- a/examples/extension/groups_extension.py +++ b/examples/extension/groups_extension.py @@ -12,7 +12,9 @@ from symphony.bdk.gen.group_model.status import Status from symphony.bdk.gen.group_model.update_group import UpdateGroup -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) async def run(): @@ -25,23 +27,41 @@ async def run(): logging.debug(f"List groups: {groups}") # list groups with cursor based pagination - groups_generator = await group_service.list_all_groups(status=Status("ACTIVE"), chunk_size=2, max_number=4) + groups_generator = await group_service.list_all_groups( + status=Status("ACTIVE"), chunk_size=2, max_number=4 + ) groups = [group async for group in groups_generator] logging.debug(f"List groups: {groups}") # create a new group profile = BaseProfile(display_name="Mary's SDL") member = Member(member_tenant=190, member_id=13056700580915) - create_group = CreateGroup(type="SDL", owner_type=Owner(value="TENANT"), owner_id=190, name="Another SDL", - members=[member], profile=profile) + create_group = CreateGroup( + type="SDL", + owner_type=Owner(value="TENANT"), + owner_id=190, + name="Another SDL", + members=[member], + profile=profile, + ) group = await group_service.insert_group(create_group=create_group) logging.debug(f"Group created: {group}") # update group name - update_group = UpdateGroup(name="Updated name", type=group.type, owner_type=Owner(value="TENANT"), - owner_id=group.owner_id, id=group.id, e_tag=group.e_tag, - status=Status(value="ACTIVE"), profile=profile, members=[member]) - group = await group_service.update_group(if_match=group.e_tag, group_id=group.id, update_group=update_group) + update_group = UpdateGroup( + name="Updated name", + type=group.type, + owner_type=Owner(value="TENANT"), + owner_id=group.owner_id, + id=group.id, + e_tag=group.e_tag, + status=Status(value="ACTIVE"), + profile=profile, + members=[member], + ) + group = await group_service.update_group( + if_match=group.e_tag, group_id=group.id, update_group=update_group + ) logging.debug(f"Group after name update: {group}") # add member to a group @@ -58,10 +78,20 @@ async def run(): logging.debug(f"Retrieve group by id: {group}") # Delete group - update_group = UpdateGroup(name=group.name, type=group.type, owner_type=Owner(value="TENANT"), - owner_id=group.owner_id, id=group.id, e_tag=group.e_tag, - status=Status(value="DELETED"), profile=profile, members=[member]) - group = await group_service.update_group(if_match=group.e_tag, group_id=group.id, update_group=update_group) + update_group = UpdateGroup( + name=group.name, + type=group.type, + owner_type=Owner(value="TENANT"), + owner_id=group.owner_id, + id=group.id, + e_tag=group.e_tag, + status=Status(value="DELETED"), + profile=profile, + members=[member], + ) + group = await group_service.update_group( + if_match=group.e_tag, group_id=group.id, update_group=update_group + ) logging.debug(f"Group removed: {group}") diff --git a/examples/multiple_instances/injector.py b/examples/multiple_instances/injector.py index 5009c882..8658ea07 100644 --- a/examples/multiple_instances/injector.py +++ b/examples/multiple_instances/injector.py @@ -1,5 +1,5 @@ -"""A bot (named benchmark-injector) sending messages to a room and checking that all messages have been replied to. -""" +"""A bot (named benchmark-injector) sending messages to a room and checking that all messages have been replied to.""" + import asyncio import logging.config from datetime import datetime @@ -27,13 +27,21 @@ async def run(): stream_id = await create_test_room(bdk.streams()) await send_messages(bdk.messages(), stream_id) - await check_all_messages_have_been_replied_to(bdk.messages(), config.bot.username, stream_id) + await check_all_messages_have_been_replied_to( + bdk.messages(), config.bot.username, stream_id + ) async def create_test_room(streams): start_date = datetime.now().isoformat() room = await streams.create_room( - V3RoomAttributes(name=f"Test-{start_date}", description=f"Test-{start_date}", public=True, discoverable=True)) + V3RoomAttributes( + name=f"Test-{start_date}", + description=f"Test-{start_date}", + public=True, + discoverable=True, + ) + ) stream_id = room.room_system_info.id await streams.add_member_to_room(REPLIER_BOT_ID, stream_id) @@ -45,18 +53,26 @@ async def create_test_room(streams): async def send_messages(messages, stream_id): for i in range(NUMBER_OF_MESSAGES): - await messages.send_message(stream_id, f"{i} {datetime.now().isoformat()}") + await messages.send_message( + stream_id, f"{i} {datetime.now().isoformat()}" + ) logging.debug("Messages sent") -async def check_all_messages_have_been_replied_to(message_service: MessageService, injector_bot_username, stream_id): +async def check_all_messages_have_been_replied_to( + message_service: MessageService, injector_bot_username, stream_id +): messages = await message_service.list_messages(stream_id, 0, 0, 2 * NUMBER_OF_MESSAGES) while len(messages) != 2 * NUMBER_OF_MESSAGES: sent_ids = extract_ids(filter(lambda m: m.user.username == injector_bot_username, messages)) replied_ids = extract_ids(filter(lambda m: m.user.user_id == REPLIER_BOT_ID, messages)) - logging.debug("Not replied to all messages yet (%s/%s), missing messages: %s", len(messages), - 2 * NUMBER_OF_MESSAGES, sent_ids - replied_ids) + logging.debug( + "Not replied to all messages yet (%s/%s), missing messages: %s", + len(messages), + 2 * NUMBER_OF_MESSAGES, + sent_ids - replied_ids, + ) await asyncio.sleep(0.1) messages = await message_service.list_messages(stream_id, 0, 0, 2 * NUMBER_OF_MESSAGES) logging.debug("Replied to all messages") @@ -66,7 +82,9 @@ def extract_ids(iterable): return set(map(lambda m: get_text_content_from_message(m).split(" ")[0], iterable)) -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) if __name__ == "__main__": asyncio.run(run()) diff --git a/examples/multiple_instances/reader.py b/examples/multiple_instances/reader.py index ac354d5a..f137dfdf 100644 --- a/examples/multiple_instances/reader.py +++ b/examples/multiple_instances/reader.py @@ -1,6 +1,7 @@ """A simple bot (named benchmark-reader), reading a datafeed and replying to a message with the same content. The goal is to run multiple instances of it, hence it adds a unique id and uses a random color in the reply. """ + import asyncio import logging.config import uuid @@ -20,12 +21,11 @@ class EventListener(RealTimeEventListener): - def __init__(self, messages: MessageService, hz_client: HazelcastClient): self._messages = messages self._client = hz_client self._id = str(uuid.uuid4()) - self._color = hex(randrange(0xffffff + 1))[2:] # substring to remove leading "0x" + self._color = hex(randrange(0xFFFFFF + 1))[2:] # substring to remove leading "0x" async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): lock = self._client.cp_subsystem.get_lock(event.message.stream.stream_id).blocking() @@ -52,7 +52,7 @@ async def _reply_to_message(self, event): processed_events.put(message.message_id, message_number, 60) async def _send_reply_message(self, stream_id, text_message): - reply_message = f"{text_message} {self._id}" + reply_message = f'{text_message} {self._id}' await self._messages.send_message(stream_id, reply_message) @@ -64,7 +64,9 @@ async def run(hz_client): await datafeed.start() -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) if __name__ == "__main__": client = hazelcast.HazelcastClient() diff --git a/examples/services/connection.py b/examples/services/connection.py index 65c4aa22..8d5eeb1e 100644 --- a/examples/services/connection.py +++ b/examples/services/connection.py @@ -16,6 +16,8 @@ async def run(): logging.info(user_connection) -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) asyncio.run(run()) diff --git a/examples/services/message_update.py b/examples/services/message_update.py index 9df8ea89..d3a7d663 100644 --- a/examples/services/message_update.py +++ b/examples/services/message_update.py @@ -1,12 +1,11 @@ import asyncio import logging.config +import time from pathlib import Path from symphony.bdk.core.activity.command import CommandContext from symphony.bdk.core.config.loader import BdkConfigLoader from symphony.bdk.core.symphony_bdk import SymphonyBdk -import time - from symphony.bdk.gen.agent_model.v4_message import V4Message @@ -17,18 +16,37 @@ async def run(): @activities.slash("/go") async def on_hello(context: CommandContext): - pick_emoji = ["cat", "dromedary_camel", "dolphin", "dog", "hamster", "goat", "panda_face", "koala", "frog", "penguin"] - - previous_message: V4Message = await messages.send_message(context.stream_id, "Let the show begin!") + pick_emoji = [ + "cat", + "dromedary_camel", + "dolphin", + "dog", + "hamster", + "goat", + "panda_face", + "koala", + "frog", + "penguin", + ] + + previous_message: V4Message = await messages.send_message( + context.stream_id, "Let the show begin!" + ) for i in range(0, len(pick_emoji)): time.sleep(1) - mml = "

Update #{}".format(pick_emoji[i], (i + 1)) - previous_message = await messages.update_message(previous_message.stream.stream_id, previous_message.message_id, mml) + mml = '

Update #{}'.format( + pick_emoji[i], (i + 1) + ) + previous_message = await messages.update_message( + previous_message.stream.stream_id, previous_message.message_id, mml + ) await bdk.datafeed().start() -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) asyncio.run(run()) diff --git a/examples/services/messages.py b/examples/services/messages.py index 9c24bece..2210c27e 100644 --- a/examples/services/messages.py +++ b/examples/services/messages.py @@ -22,28 +22,43 @@ async def run(): await message_service.send_message(stream_id_1, "Hello, World!") - with open("/path/to/attachment1", "rb") as file1, \ - open("/path/to/attachment2", "rb") as file2: - message = Message(content="Hello, World!", attachments=[file1, file2]) + with ( + open("/path/to/attachment1", "rb") as file1, + open("/path/to/attachment2", "rb") as file2, + ): + message = Message( + content="Hello, World!", attachments=[file1, file2] + ) await message_service.blast_message([stream_id_1, stream_id_2], message) - with open("/path/to/attachment", "rb") as attachment, \ - open("/path/to/attachment-preview", "rb") as preview: - message = Message(content="Hello, World!", attachments=[(attachment, preview)]) + with ( + open("/path/to/attachment", "rb") as attachment, + open("/path/to/attachment-preview", "rb") as preview, + ): + message = Message( + content="Hello, World!", attachments=[(attachment, preview)] + ) await message_service.blast_message([stream_id_1, stream_id_2], message) - async for m in await message_service.search_all_messages(MessageSearchQuery(text="some_text", - stream_id=stream_id_1)): + async for m in await message_service.search_all_messages( + MessageSearchQuery(text="some_text", stream_id=stream_id_1) + ): logging.debug(m.message_id) # import a message wih attachments content = "symphony" encoded_content = base64.b64encode(content.encode("ascii")) - attachment = V4ImportedMessageAttachment(filename="text.txt", content=encoded_content.decode("ascii")) - msg = V4ImportedMessage(intended_message_timestamp=1647353689268, intended_message_from_user_id=13056700580915, - originating_system_id="fooChat", stream_id=stream_id_1, - message="This is an imported message!", - attachments=[attachment]) + attachment = V4ImportedMessageAttachment( + filename="text.txt", content=encoded_content.decode("ascii") + ) + msg = V4ImportedMessage( + intended_message_timestamp=1647353689268, + intended_message_from_user_id=13056700580915, + originating_system_id="fooChat", + stream_id=stream_id_1, + message="This is an imported message!", + attachments=[attachment], + ) await message_service.import_messages([msg]) logging.info("Obo example:") @@ -53,8 +68,13 @@ async def run(): # https://docs.developers.symphony.com/building-bots-on-symphony/datafeed/overview-of-streams await obo_services.messages().suppress_message("URL-Safe MessageID") obo_message = await obo_services.messages().send_message(stream_id_1, "Hello obo") - await obo_services.messages().update_message(stream_id_1, obo_message.message_id, "Hello obo updated") + await obo_services.messages().update_message( + stream_id_1, obo_message.message_id, "Hello obo updated" + ) -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) + +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) asyncio.run(run()) diff --git a/examples/services/signals.py b/examples/services/signals.py index 44b5b868..e720d537 100644 --- a/examples/services/signals.py +++ b/examples/services/signals.py @@ -13,17 +13,23 @@ async def run(): async with SymphonyBdk(config) as bdk: signal_service = bdk.signals() - logging.info('Creating new signal.') + logging.info("Creating new signal.") signal = await signal_service.create_signal( - BaseSignal(name="Testing signal", query="HASHTAG:hashtag", visible_on_profile=False, company_wide=False)) + BaseSignal( + name="Testing signal", + query="HASHTAG:hashtag", + visible_on_profile=False, + company_wide=False, + ) + ) logging.info(signal) logging.info(await signal_service.get_signal(signal.id)) - logging.info('Add a subscriber to the signal.') + logging.info("Add a subscriber to the signal.") await signal_service.subscribe_users_to_signal(signal.id, True, [13056700580913]) - logging.info('Unsubscribe added user to the signal.') + logging.info("Unsubscribe added user to the signal.") await signal_service.unsubscribe_users_to_signal(signal.id, [13056700580913]) logging.info("Listing all signals") @@ -37,6 +43,8 @@ async def run(): logging.info(await signal_service.delete_signal(signal.id)) -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) asyncio.run(run()) diff --git a/examples/services/streams.py b/examples/services/streams.py index 0c90c312..b3260e19 100644 --- a/examples/services/streams.py +++ b/examples/services/streams.py @@ -30,12 +30,19 @@ async def run(): logging.debug(m) stream_filter = StreamFilter( - stream_types=[StreamType(type="IM"), StreamType(type="MIM"), StreamType(type="ROOM")], - include_inactive_streams=False) + stream_types=[ + StreamType(type="IM"), + StreamType(type="MIM"), + StreamType(type="ROOM"), + ], + include_inactive_streams=False, + ) async for s in await streams.list_all_streams(stream_filter): logging.debug(s) -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) asyncio.run(run()) diff --git a/examples/services/users.py b/examples/services/users.py index 26c0f169..216f2124 100644 --- a/examples/services/users.py +++ b/examples/services/users.py @@ -21,12 +21,14 @@ async def run(): async for uid in await user_service.search_all_users(query, local=False): logging.debug(uid) - async for i in await user_service.list_all_user_details_by_filter(user_filter=UserFilter(status="ENABLED", - role="INDIVIDUAL"), - max_number=100): + async for i in await user_service.list_all_user_details_by_filter( + user_filter=UserFilter(status="ENABLED", role="INDIVIDUAL"), max_number=100 + ): logging.debug(i.user_attributes.display_name) -logging.config.fileConfig(Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False) +logging.config.fileConfig( + Path(__file__).parent.parent / "logging.conf", disable_existing_loggers=False +) asyncio.run(run()) diff --git a/poetry.lock b/poetry.lock index b2a6f7e5..620cb2bd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -119,7 +119,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] +speedups = ["Brotli", "aiodns (>=3.3.0)", "brotlicffi"] [[package]] name = "aiosignal" @@ -149,26 +149,6 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] -[[package]] -name = "astroid" -version = "2.15.8" -description = "An abstract syntax tree for Python with inference support." -optional = false -python-versions = ">=3.7.2" -groups = ["dev"] -files = [ - {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, - {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, -] - -[package.dependencies] -lazy-object-proxy = ">=1.4.0" -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} -wrapt = [ - {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, - {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, -] - [[package]] name = "async-timeout" version = "4.0.3" @@ -195,12 +175,12 @@ files = [ ] [package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\""] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "babel" @@ -332,6 +312,18 @@ files = [ [package.dependencies] pycparser = "*" +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + [[package]] name = "charset-normalizer" version = "3.4.0" @@ -555,7 +547,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] +toml = ["tomli"] [[package]] name = "cryptography" @@ -608,10 +600,10 @@ files = [ cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""] -pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] @@ -630,21 +622,17 @@ files = [ ] [[package]] -name = "dill" -version = "0.3.9" -description = "serialize all of Python" +name = "distlib" +version = "0.4.0" +description = "Distribution utilities" optional = false -python-versions = ">=3.8" +python-versions = "*" groups = ["dev"] files = [ - {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, - {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] -[package.extras] -graph = ["objgraph (>=1.7.2)"] -profile = ["gprof2dot (>=2022.7.29)"] - [[package]] name = "docutils" version = "0.16" @@ -693,6 +681,23 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "filelock" +version = "3.18.0" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, + {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] +typing = ["typing-extensions (>=4.12.2)"] + [[package]] name = "frozenlist" version = "1.5.0" @@ -828,6 +833,21 @@ files = [ [package.extras] stats = ["psutil"] +[[package]] +name = "identify" +version = "2.6.13" +description = "File identification library for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, + {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.10" @@ -872,12 +892,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -892,21 +912,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -groups = ["dev"] -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - [[package]] name = "jinja2" version = "3.1.6" @@ -925,53 +930,6 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] -[[package]] -name = "lazy-object-proxy" -version = "1.10.0" -description = "A fast and thorough lazy object proxy." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, - {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, -] - [[package]] name = "liccheck" version = "0.9.2" @@ -1058,18 +1016,6 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - [[package]] name = "multidict" version = "6.1.0" @@ -1175,6 +1121,18 @@ files = [ [package.dependencies] typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + [[package]] name = "nulltype" version = "2.3.1" @@ -1235,6 +1193,25 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pre-commit" +version = "4.3.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8"}, + {file = "pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + [[package]] name = "propcache" version = "0.2.0" @@ -1389,36 +1366,6 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] -[[package]] -name = "pylint" -version = "2.17.7" -description = "python code static checker" -optional = false -python-versions = ">=3.7.2" -groups = ["dev"] -files = [ - {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, - {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, -] - -[package.dependencies] -astroid = ">=2.15.8,<=2.17.0-dev0" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = [ - {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, -] -isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -tomlkit = ">=0.10.1" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - -[package.extras] -spelling = ["pyenchant (>=3.2,<4.0)"] -testutils = ["gitpython (>3)"] - [[package]] name = "pyparsing" version = "3.1.4" @@ -1516,7 +1463,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1692,6 +1639,34 @@ files = [ {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, ] +[[package]] +name = "ruff" +version = "0.12.8" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513"}, + {file = "ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc"}, + {file = "ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46"}, + {file = "ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3"}, + {file = "ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e"}, + {file = "ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749"}, + {file = "ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033"}, +] + [[package]] name = "safety" version = "2.3.5" @@ -1729,7 +1704,7 @@ files = [ ] [package.extras] -dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1) ; python_version == \"3.4\"", "coverage", "flake8", "nose2", "readme-renderer (<25.0) ; python_version == \"3.4\"", "tox", "wheel", "zest.releaser[recommended]"] +dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1)", "coverage", "flake8", "nose2", "readme-renderer (<25.0)", "tox", "wheel", "zest.releaser[recommended]"] doc = ["Sphinx", "sphinx-rtd-theme"] [[package]] @@ -1745,13 +1720,13 @@ files = [ ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] +core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" @@ -1823,7 +1798,7 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast ; python_version < \"3.8\""] +test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] [[package]] name = "sphinx-basic-ng" @@ -1973,36 +1948,24 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version < \"3.11\"" +markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] -[[package]] -name = "tomlkit" -version = "0.13.2" -description = "Style preserving TOML library" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, - {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, -] - [[package]] name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main"] +markers = "python_version < \"3.13\"" files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] -markers = {main = "python_version < \"3.13\"", dev = "python_version < \"3.11\""} [[package]] name = "urllib3" @@ -2017,91 +1980,32 @@ files = [ ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." +name = "virtualenv" +version = "20.33.1" +description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, + {file = "virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67"}, + {file = "virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8"}, ] +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + [[package]] name = "yarl" version = "1.18.3" @@ -2213,14 +2117,14 @@ files = [ ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">3.9.0,<3.9.1 || >3.9.1,<4.0" -content-hash = "4c805d8f484bc4370ee935053b1c4e162b6879612e38baa4d3130c7724e86016" +content-hash = "e2a8515fbed1fa81249ae4c081412df1a60c5c5251c4b46400c14d25ee7bfc2c" diff --git a/pyproject.toml b/pyproject.toml index 5fc52c5b..07f99b97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ docutils = "0.16" [tool.poetry.group.dev.dependencies] pytest = "^8.3.3" -pylint = "^2.6.0" pytest-cov = "^5.0.0" pytest-asyncio = "^0.24.0" Sphinx = "^4.4.0" @@ -38,6 +37,8 @@ safety = "^2.3.5" setuptools = "^79.0.0" liccheck = "^0.9.2" coverage = {version = "^6.0b1", extras = ["toml"]} +ruff = "^0.12.8" +pre-commit = "^4.3.0" [tool.poetry.requires-plugins] poetry-plugin-export = ">=1.8" @@ -70,3 +71,22 @@ authorized_licenses = [ "Apache-2.0", "Python Software Foundation" ] + + + +[tool.ruff] +include = ["pyproject.toml", "symphony/**/*.py", "examples/**/*.py"] +exclude = [ + "symphony/bdk/gen", +] + +target-version = "py39" +line-length = 100 + +[tool.ruff.lint] +select = ["E", "W", "F", "I"] +ignore = ["E501"] + +[tool.ruff.format] +quote-style = "double" + diff --git a/symphony/bdk/core/__init__.py b/symphony/bdk/core/__init__.py index 0b03fa19..2ffdd646 100644 --- a/symphony/bdk/core/__init__.py +++ b/symphony/bdk/core/__init__.py @@ -4,6 +4,7 @@ proxy on Windows with Python 3.8+. As known workaround, the following code is meant to avoid this issue by detecting the operating system and setting WindowsSelectorEventLoopPolicy as event loop policy. """ + import asyncio import os diff --git a/symphony/bdk/core/activity/__init__.py b/symphony/bdk/core/activity/__init__.py index 35770d97..e62625c5 100644 --- a/symphony/bdk/core/activity/__init__.py +++ b/symphony/bdk/core/activity/__init__.py @@ -1,2 +1 @@ -"""Package containing all modules related to the Activity API -""" +"""Package containing all modules related to the Activity API""" diff --git a/symphony/bdk/core/activity/api.py b/symphony/bdk/core/activity/api.py index 86b4ebab..2317f020 100644 --- a/symphony/bdk/core/activity/api.py +++ b/symphony/bdk/core/activity/api.py @@ -1,10 +1,10 @@ from abc import ABC, abstractmethod -from typing import TypeVar, Generic +from typing import Generic, TypeVar from symphony.bdk.gen.agent_model.v4_initiator import V4Initiator -C = TypeVar('C') # Context type -E = TypeVar('E') # Event type +C = TypeVar("C") # Context type +E = TypeVar("E") # Event type class ActivityContext(Generic[E]): @@ -60,7 +60,7 @@ async def on_activity(self, context: C): """ def before_matcher(self, context: C): - """ This callback can be used to prepare :py:class:`ActivityContext` before actually processing the `matches` + """This callback can be used to prepare :py:class:`ActivityContext` before actually processing the `matches` method. :param context: an instance of :py:class:`ActivityContext` diff --git a/symphony/bdk/core/activity/command.py b/symphony/bdk/core/activity/command.py index e068db3a..05b4a56c 100644 --- a/symphony/bdk/core/activity/command.py +++ b/symphony/bdk/core/activity/command.py @@ -14,11 +14,15 @@ class CommandContext(ActivityContext[V4MessageSent]): - """Default implementation of the :py:class:`ActivityContext` handled by the :py:class:`CommandActivity`. - """ - - def __init__(self, initiator: V4Initiator, source_event: V4MessageSent, bot_display_name: str, - bot_user_id: int = None): + """Default implementation of the :py:class:`ActivityContext` handled by the :py:class:`CommandActivity`.""" + + def __init__( + self, + initiator: V4Initiator, + source_event: V4MessageSent, + bot_display_name: str, + bot_user_id: int = None, + ): self._message_id = source_event.message.message_id self._stream_id = source_event.message.stream.stream_id self._bot_display_name = bot_display_name @@ -56,8 +60,7 @@ def arguments(self) -> Arguments: class CommandActivity(AbstractActivity[CommandContext]): - """A command activity corresponds to any message sent in a chat where the bot is part of. - """ + """A command activity corresponds to any message sent in a chat where the bot is part of.""" def __init__(self): self._bot_user_id = None @@ -103,15 +106,20 @@ def __init__(self, name, requires_mention_bot, callback, description=""): if self._requires_mention_bot: # The user id will be loaded in runtime in a lazy way - self._command_pattern.prepend_token(MatchingUserIdMentionToken(lambda: self.bot_user_id)) + self._command_pattern.prepend_token( + MatchingUserIdMentionToken(lambda: self.bot_user_id) + ) @property def name(self) -> str: return self._name def build_command_description(self) -> str: - return self._description + " (mention required)" if self._requires_mention_bot \ + return ( + self._description + " (mention required)" + if self._requires_mention_bot else self._description + " (mention not required)" + ) def matches(self, context: CommandContext) -> bool: match_result = self._command_pattern.get_match_result(context.source_event.message) diff --git a/symphony/bdk/core/activity/exception.py b/symphony/bdk/core/activity/exception.py index 53049221..e69a471d 100644 --- a/symphony/bdk/core/activity/exception.py +++ b/symphony/bdk/core/activity/exception.py @@ -1,7 +1,5 @@ -"""Module containing all activities related exception. -""" +"""Module containing all activities related exception.""" class FatalActivityExecutionException(Exception): - """A Fatal error occurred during an activity execution flow - """ + """A Fatal error occurred during an activity execution flow""" diff --git a/symphony/bdk/core/activity/help_command.py b/symphony/bdk/core/activity/help_command.py index d247f831..4b450ec7 100644 --- a/symphony/bdk/core/activity/help_command.py +++ b/symphony/bdk/core/activity/help_command.py @@ -18,5 +18,6 @@ async def on_activity(self, context: CommandContext): help_message = "" for act in activity_list: help_message += "
  • " + act.name + " - " + act.build_command_description() + "
  • " - await self._bdk.messages().send_message(context.stream_id, - "
      " + help_message + "
    ") + await self._bdk.messages().send_message( + context.stream_id, "
      " + help_message + "
    " + ) diff --git a/symphony/bdk/core/activity/parsing/arguments.py b/symphony/bdk/core/activity/parsing/arguments.py index d9397a8d..0669d4b6 100644 --- a/symphony/bdk/core/activity/parsing/arguments.py +++ b/symphony/bdk/core/activity/parsing/arguments.py @@ -1,4 +1,4 @@ -from symphony.bdk.core.activity.parsing.message_entities import Mention, Hashtag, Cashtag +from symphony.bdk.core.activity.parsing.message_entities import Cashtag, Hashtag, Mention class Arguments: diff --git a/symphony/bdk/core/activity/parsing/command_token.py b/symphony/bdk/core/activity/parsing/command_token.py index 3a72be45..6316613c 100644 --- a/symphony/bdk/core/activity/parsing/command_token.py +++ b/symphony/bdk/core/activity/parsing/command_token.py @@ -1,5 +1,4 @@ -"""This module gathers all token classes that we can use for {@link SlashCommandPattern} -""" +"""This module gathers all token classes that we can use for {@link SlashCommandPattern}""" import re from abc import ABC, abstractmethod @@ -151,5 +150,7 @@ def matches(self, token: object): return isinstance(token, Mention) and token.user_id == self._matching_user_id_supplier() def __eq__(self, o: object) -> bool: - return isinstance(o, MatchingUserIdMentionToken) and \ - self._matching_user_id_supplier() == o._matching_user_id_supplier() + return ( + isinstance(o, MatchingUserIdMentionToken) + and self._matching_user_id_supplier() == o._matching_user_id_supplier() + ) diff --git a/symphony/bdk/core/activity/parsing/input_tokenizer.py b/symphony/bdk/core/activity/parsing/input_tokenizer.py index ba7b10d2..ae977262 100644 --- a/symphony/bdk/core/activity/parsing/input_tokenizer.py +++ b/symphony/bdk/core/activity/parsing/input_tokenizer.py @@ -23,7 +23,7 @@ def __init__(self, message: V4Message): :param message: message to be tokenized. """ self._document = fromstring(message.message) - json_data = message.data if hasattr(message, 'data') and message.data else "{}" + json_data = message.data if hasattr(message, "data") and message.data else "{}" self._data_node = json.loads(json_data) self._buffer = "" self._tokens = [] @@ -43,7 +43,7 @@ def _parse_xml_text(self, root): if self._is_entity_node(root): self._parse_buffer() entity_id = root.attrib[InputTokenizer.data_entity_id] - entity_type = self._data_node[entity_id]['type'] + entity_type = self._data_node[entity_id]["type"] if self._is_entity_type_supported(entity_type): self._parse_entity_node(entity_id, root) else: @@ -91,14 +91,22 @@ def _is_entity_node(self, root): :param root: root of the xml document :return: Whether the node is a known entity. """ - if root.tag == "span" and "class" in root.attrib and root.attrib["class"] == "entity" \ - and InputTokenizer.data_entity_id in root.attrib: + if ( + root.tag == "span" + and "class" in root.attrib + and root.attrib["class"] == "entity" + and InputTokenizer.data_entity_id in root.attrib + ): return root.attrib[InputTokenizer.data_entity_id] in self._data_node return False @staticmethod def _is_entity_type_supported(entity_type): - return entity_type in ["org.symphonyoss.taxonomy", "org.symphonyoss.fin.security", "com.symphony.user.mention"] + return entity_type in [ + "org.symphonyoss.taxonomy", + "org.symphonyoss.fin.security", + "com.symphony.user.mention", + ] @property def tokens(self): diff --git a/symphony/bdk/core/activity/parsing/match_result.py b/symphony/bdk/core/activity/parsing/match_result.py index 1d7880af..e5057eaa 100644 --- a/symphony/bdk/core/activity/parsing/match_result.py +++ b/symphony/bdk/core/activity/parsing/match_result.py @@ -24,6 +24,8 @@ def arguments(self): return self._arguments def __eq__(self, other): - return isinstance(other, MatchResult) \ - and self._is_matching == other._is_matching \ - and self._arguments == other._arguments + return ( + isinstance(other, MatchResult) + and self._is_matching == other._is_matching + and self._arguments == other._arguments + ) diff --git a/symphony/bdk/core/activity/parsing/message_entities.py b/symphony/bdk/core/activity/parsing/message_entities.py index a6dd7fde..8fe06a3d 100644 --- a/symphony/bdk/core/activity/parsing/message_entities.py +++ b/symphony/bdk/core/activity/parsing/message_entities.py @@ -1,5 +1,4 @@ -"""This module gathers all entities that we can find in a message. -""" +"""This module gathers all entities that we can find in a message.""" class Cashtag: @@ -67,7 +66,9 @@ def user_id(self): return self._user_id def __eq__(self, o: object) -> bool: - return isinstance(o, Mention) \ - and self._text == o._text \ - and self._user_display_name == o._user_display_name \ - and self._user_id == o._user_id + return ( + isinstance(o, Mention) + and self._text == o._text + and self._user_display_name == o._user_display_name + and self._user_id == o._user_id + ) diff --git a/symphony/bdk/core/activity/parsing/slash_command_pattern.py b/symphony/bdk/core/activity/parsing/slash_command_pattern.py index 6bffd37a..ebd2e8cd 100644 --- a/symphony/bdk/core/activity/parsing/slash_command_pattern.py +++ b/symphony/bdk/core/activity/parsing/slash_command_pattern.py @@ -1,7 +1,13 @@ import re -from symphony.bdk.core.activity.parsing.command_token import StaticCommandToken, StringArgumentCommandToken, \ - HashArgumentCommandToken, CashArgumentCommandToken, MentionArgumentCommandToken, ArgumentCommandToken +from symphony.bdk.core.activity.parsing.command_token import ( + ArgumentCommandToken, + CashArgumentCommandToken, + HashArgumentCommandToken, + MentionArgumentCommandToken, + StaticCommandToken, + StringArgumentCommandToken, +) from symphony.bdk.core.activity.parsing.input_tokenizer import InputTokenizer from symphony.bdk.core.activity.parsing.match_result import MatchResult from symphony.bdk.gen.agent_model.v4_message import V4Message @@ -23,10 +29,15 @@ class SlashCommandPattern: whitespace_pattern = re.compile(r"\s+") def __init__(self, pattern: str): - self._tokens = [self.create_token(pattern) for pattern in - re.split(SlashCommandPattern.whitespace_pattern, pattern) if pattern] - - filtered_tokens_arg_names = list(filter(lambda a: a is not None, map(self.get_arg_name, self._tokens))) + self._tokens = [ + self.create_token(pattern) + for pattern in re.split(SlashCommandPattern.whitespace_pattern, pattern) + if pattern + ] + + filtered_tokens_arg_names = list( + filter(lambda a: a is not None, map(self.get_arg_name, self._tokens)) + ) if not len(set(filtered_tokens_arg_names)) == len(filtered_tokens_arg_names): raise ValueError("Argument names must be unique") @@ -67,7 +78,11 @@ def get_match_result(self, message: V4Message): """ input_tokenizer = InputTokenizer(message) input_tokens = input_tokenizer.tokens - return MatchResult(True, self.get_arguments(input_tokens)) if self.matches(input_tokens) else MatchResult(False) + return ( + MatchResult(True, self.get_arguments(input_tokens)) + if self.matches(input_tokens) + else MatchResult(False) + ) def matches(self, input_tokens): """ diff --git a/symphony/bdk/core/activity/registry.py b/symphony/bdk/core/activity/registry.py index b973e49a..7390c017 100644 --- a/symphony/bdk/core/activity/registry.py +++ b/symphony/bdk/core/activity/registry.py @@ -1,16 +1,18 @@ import logging -from symphony.bdk.gen.agent_model.v4_user_joined_room import V4UserJoinedRoom - from symphony.bdk.core.activity.api import AbstractActivity from symphony.bdk.core.activity.command import CommandActivity, CommandContext, SlashCommandActivity -from symphony.bdk.core.activity.form import FormReplyContext, FormReplyActivity -from symphony.bdk.core.activity.user_joined_room import UserJoinedRoomContext, UserJoinedRoomActivity +from symphony.bdk.core.activity.form import FormReplyActivity, FormReplyContext +from symphony.bdk.core.activity.user_joined_room import ( + UserJoinedRoomActivity, + UserJoinedRoomContext, +) from symphony.bdk.core.service.datafeed.real_time_event_listener import RealTimeEventListener from symphony.bdk.core.service.session.session_service import SessionService from symphony.bdk.gen.agent_model.v4_initiator import V4Initiator from symphony.bdk.gen.agent_model.v4_message_sent import V4MessageSent from symphony.bdk.gen.agent_model.v4_symphony_elements_action import V4SymphonyElementsAction +from symphony.bdk.gen.agent_model.v4_user_joined_room import V4UserJoinedRoom logger = logging.getLogger(__name__) @@ -22,6 +24,7 @@ def _initialize_display_name(func): :param func: the function to be decorated :return: the decorated function """ + async def decorator(*args, **kwargs): registry = args[0] await registry.fetch_bot_info() @@ -59,7 +62,9 @@ def _pre_process_activity(self, activity: AbstractActivity): for act in self._activity_list: if act == activity: self._activity_list.remove(act) - logger.debug("Activity '%s' has been removed/unsubscribed in order to be replaced", act) + logger.debug( + "Activity '%s' has been removed/unsubscribed in order to be replaced", act + ) def slash(self, command: str, mention_bot: bool = True, description: str = ""): """Decorator around a listener callback coroutine which takes a @@ -72,8 +77,11 @@ def slash(self, command: str, mention_bot: bool = True, description: str = ""): :param description: command description :return: None """ + def decorator(func): - logger.debug("Registering slash command with command=%s, mention_bot=%s", command, mention_bot) + logger.debug( + "Registering slash command with command=%s, mention_bot=%s", command, mention_bot + ) self.register(SlashCommandActivity(command, mention_bot, func, description)) return func @@ -89,7 +97,9 @@ async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): await act.on_activity(context) @_initialize_display_name - async def on_symphony_elements_action(self, initiator: V4Initiator, event: V4SymphonyElementsAction): + async def on_symphony_elements_action( + self, initiator: V4Initiator, event: V4SymphonyElementsAction + ): context = FormReplyContext(initiator, event) for act in self._activity_list: if isinstance(act, FormReplyActivity): diff --git a/symphony/bdk/core/auth/__init__.py b/symphony/bdk/core/auth/__init__.py index 65171afc..01b3c616 100644 --- a/symphony/bdk/core/auth/__init__.py +++ b/symphony/bdk/core/auth/__init__.py @@ -1,2 +1 @@ -"""Package containing all modules related to authentication -""" +"""Package containing all modules related to authentication""" diff --git a/symphony/bdk/core/auth/auth_session.py b/symphony/bdk/core/auth/auth_session.py index a0b53608..dce03a31 100644 --- a/symphony/bdk/core/auth/auth_session.py +++ b/symphony/bdk/core/auth/auth_session.py @@ -1,13 +1,11 @@ -"""Module containing session handle classes. +"""Module containing session handle classes.""" -""" -from datetime import datetime, timezone import logging +from datetime import datetime, timezone from symphony.bdk.core.auth.exception import AuthInitializationError from symphony.bdk.core.auth.jwt_helper import extract_token_claims - logger = logging.getLogger(__name__) EXPIRATION_SAFETY_BUFFER_SECONDS = 5 @@ -32,8 +30,7 @@ def __init__(self, authenticator): self._expire_at = -1 async def refresh(self): - """Trigger re-authentication to refresh the tokens. - """ + """Trigger re-authentication to refresh the tokens.""" logger.debug("Authenticate") self._session_token = await self._authenticator.retrieve_session_token() if await self.skd_enabled: @@ -103,14 +100,12 @@ def key_manager_token(self, value): @property async def skd_enabled(self): - token_data = extract_token_claims(await self.session_token) if not token_data.get(SKD_FLAG_NAME, False): return False return await self._authenticator.agent_version_service.is_skd_supported() - class OboAuthSession(AuthSession): """RSA OBO Authentication session handle to get the OBO session token from. It uses an :class:`symphony.bdk.core.auth.obo_authenticator.OboAuthenticator` to actually retrieve the tokens when @@ -127,21 +122,26 @@ def __init__(self, authenticator, user_id: int = None, username: str = None): """ super().__init__(authenticator) if user_id is not None and username is not None: - raise AuthInitializationError("Username and user id for OBO authentication should not be defined at " - "a same time.") + raise AuthInitializationError( + "Username and user id for OBO authentication should not be defined at a same time." + ) if user_id is None and username is None: - raise AuthInitializationError("At least username or user id should be defined for " - "OBO authentication.") + raise AuthInitializationError( + "At least username or user id should be defined for OBO authentication." + ) self.user_id = user_id self.username = username async def refresh(self): - """Trigger re-authentication to refresh the OBO session token. - """ + """Trigger re-authentication to refresh the OBO session token.""" if self.user_id is not None: - self._session_token = await self._authenticator.retrieve_obo_session_token_by_user_id(self.user_id) + self._session_token = await self._authenticator.retrieve_obo_session_token_by_user_id( + self.user_id + ) if self.username is not None: - self._session_token = await self._authenticator.retrieve_obo_session_token_by_username(self.username) + self._session_token = await self._authenticator.retrieve_obo_session_token_by_username( + self.username + ) @property async def session_token(self): diff --git a/symphony/bdk/core/auth/authenticator_factory.py b/symphony/bdk/core/auth/authenticator_factory.py index e95bd6b2..0dfc1792 100644 --- a/symphony/bdk/core/auth/authenticator_factory.py +++ b/symphony/bdk/core/auth/authenticator_factory.py @@ -1,11 +1,21 @@ -"""Module for instantiating various authenticator objects. -""" +"""Module for instantiating various authenticator objects.""" -from symphony.bdk.core.auth.bot_authenticator import BotAuthenticator, BotAuthenticatorRsa, BotAuthenticatorCert +from symphony.bdk.core.auth.bot_authenticator import ( + BotAuthenticator, + BotAuthenticatorCert, + BotAuthenticatorRsa, +) from symphony.bdk.core.auth.exception import AuthInitializationError -from symphony.bdk.core.auth.ext_app_authenticator import ExtensionAppAuthenticator, ExtensionAppAuthenticatorRsa, \ - ExtensionAppAuthenticatorCert -from symphony.bdk.core.auth.obo_authenticator import OboAuthenticator, OboAuthenticatorRsa, OboAuthenticatorCert +from symphony.bdk.core.auth.ext_app_authenticator import ( + ExtensionAppAuthenticator, + ExtensionAppAuthenticatorCert, + ExtensionAppAuthenticatorRsa, +) +from symphony.bdk.core.auth.obo_authenticator import ( + OboAuthenticator, + OboAuthenticatorCert, + OboAuthenticatorRsa, +) from symphony.bdk.core.client.api_client_factory import ApiClientFactory from symphony.bdk.core.config.model.bdk_config import BdkConfig from symphony.bdk.gen.auth_api.certificate_authentication_api import CertificateAuthenticationApi @@ -45,18 +55,20 @@ def get_bot_authenticator(self) -> BotAuthenticator: self._config.bot, self._api_client_factory.get_login_client(), self._api_client_factory.get_relay_client(), - self._config.retry + self._config.retry, ) if self._config.bot.is_certificate_configuration_valid(): return BotAuthenticatorCert( self._api_client_factory.get_session_auth_client(), self._api_client_factory.get_key_auth_client(), - self._config.retry + self._config.retry, ) - raise AuthInitializationError("RSA or certificate authentication should be configured. " - "Only one field among private key path or content should be configured " - "for bot RSA authentication. " - "The path field should be specified for bot certificate authentication.") + raise AuthInitializationError( + "RSA or certificate authentication should be configured. " + "Only one field among private key path or content should be configured " + "for bot RSA authentication. " + "The path field should be specified for bot certificate authentication." + ) def get_obo_authenticator(self) -> OboAuthenticator: """Creates a new instance of a Obo Authenticator service. @@ -68,13 +80,17 @@ def get_obo_authenticator(self) -> OboAuthenticator: return OboAuthenticatorRsa( app_config, AuthenticationApi(self._api_client_factory.get_login_client()), - self._config.retry + self._config.retry, ) if app_config.is_certificate_configuration_valid(): - authentication_api = CertificateAuthenticationApi(self._api_client_factory.get_app_session_auth_client()) + authentication_api = CertificateAuthenticationApi( + self._api_client_factory.get_app_session_auth_client() + ) return OboAuthenticatorCert(authentication_api, self._config.retry) - raise AuthInitializationError("Application under 'app' field should be configured with a private key or " - "a certificate in order to use OBO authentication.") + raise AuthInitializationError( + "Application under 'app' field should be configured with a private key or " + "a certificate in order to use OBO authentication." + ) def get_extension_app_authenticator(self) -> ExtensionAppAuthenticator: """Creates a new instance of an extension app authenticator service. @@ -88,14 +104,18 @@ def get_extension_app_authenticator(self) -> ExtensionAppAuthenticator: PodApi(self._api_client_factory.get_pod_client()), app_config.app_id, app_config.private_key, - self._config.retry + self._config.retry, ) if app_config.is_certificate_configuration_valid(): return ExtensionAppAuthenticatorCert( - CertificateAuthenticationApi(self._api_client_factory.get_app_session_auth_client()), + CertificateAuthenticationApi( + self._api_client_factory.get_app_session_auth_client() + ), CertificatePodApi(self._api_client_factory.get_app_session_auth_client()), app_config.app_id, - self._config.retry + self._config.retry, ) - raise AuthInitializationError("Application under 'app' field should be configured with a private key path or " - "content in order to authenticate extension app.") + raise AuthInitializationError( + "Application under 'app' field should be configured with a private key path or " + "content in order to authenticate extension app." + ) diff --git a/symphony/bdk/core/auth/bot_authenticator.py b/symphony/bdk/core/auth/bot_authenticator.py index 1b74bc64..74508da6 100644 --- a/symphony/bdk/core/auth/bot_authenticator.py +++ b/symphony/bdk/core/auth/bot_authenticator.py @@ -1,7 +1,7 @@ -"""Module containing BotAuthenticator classes. -""" -from typing import Optional, Tuple +"""Module containing BotAuthenticator classes.""" + from abc import ABC, abstractmethod +from typing import Optional, Tuple from symphony.bdk.core.auth.jwt_helper import create_signed_jwt, generate_expiration_time from symphony.bdk.core.config.model.bdk_bot_config import BdkBotConfig @@ -17,11 +17,14 @@ class BotAuthenticator(ABC): - """Bot authentication service. - """ - - def __init__(self, session_auth_client: ApiClient, key_manager_auth_client: ApiClient, - retry_config: BdkRetryConfig): + """Bot authentication service.""" + + def __init__( + self, + session_auth_client: ApiClient, + key_manager_auth_client: ApiClient, + retry_config: BdkRetryConfig, + ): self._session_auth_client = session_auth_client self._key_manager_auth_client = key_manager_auth_client self._retry_config = retry_config @@ -40,9 +43,7 @@ async def retrieve_session_token_object(self) -> Tuple[Token, int]: :return: retrieved token object + expiration date. """ expire_at = generate_expiration_time() - token = await self._authenticate_and_get_token_object( - self._session_auth_client, expire_at - ) + token = await self._authenticate_and_get_token_object(self._session_auth_client, expire_at) return token, expire_at async def retrieve_key_manager_token(self) -> str: @@ -70,13 +71,16 @@ def agent_version_service(self, agent_version_service: AgentVersionService): self._agent_version_service = agent_version_service - class BotAuthenticatorRsa(BotAuthenticator): - """Bot authenticator RSA implementation. - """ - - def __init__(self, bot_config: BdkBotConfig, login_api_client: ApiClient, relay_api_client: ApiClient, - retry_config: BdkRetryConfig): + """Bot authenticator RSA implementation.""" + + def __init__( + self, + bot_config: BdkBotConfig, + login_api_client: ApiClient, + relay_api_client: ApiClient, + retry_config: BdkRetryConfig, + ): self._bot_config = bot_config super().__init__(login_api_client, relay_api_client, retry_config) @@ -92,16 +96,13 @@ async def _authenticate_and_get_token_object( """Calls pubkey auth endpoint with signed jwt token. :return: token object which might contain a few tokens inside """ - jwt = create_signed_jwt( - self._bot_config.private_key, self._bot_config.username, expire_at - ) + jwt = create_signed_jwt(self._bot_config.private_key, self._bot_config.username, expire_at) req = AuthenticateRequest(token=jwt) return await AuthenticationApi(api_client).pubkey_authenticate_post(req) class BotAuthenticatorCert(BotAuthenticator): - """Bot authenticator certificate implementation. - """ + """Bot authenticator certificate implementation.""" @retry(retry=authentication_retry) async def _authenticate_and_get_token(self, api_client: ApiClient) -> str: diff --git a/symphony/bdk/core/auth/exception.py b/symphony/bdk/core/auth/exception.py index 266f1d03..5ca65613 100644 --- a/symphony/bdk/core/auth/exception.py +++ b/symphony/bdk/core/auth/exception.py @@ -1,10 +1,8 @@ -"""Module containing all authentication related exception. -""" +"""Module containing all authentication related exception.""" class AuthInitializationError(Exception): - """Thrown when unable to read/parse a RSA Private Key or a certificate. - """ + """Thrown when unable to read/parse a RSA Private Key or a certificate.""" def __init__(self, message: str): super().__init__() @@ -12,8 +10,7 @@ def __init__(self, message: str): class AuthUnauthorizedError(Exception): - """When thrown, it means that authentication cannot be performed for several reasons - """ + """When thrown, it means that authentication cannot be performed for several reasons""" def __init__(self, message: str, cause: Exception = None): super().__init__() diff --git a/symphony/bdk/core/auth/ext_app_authenticator.py b/symphony/bdk/core/auth/ext_app_authenticator.py index 8b29ef6e..9d3966b2 100644 --- a/symphony/bdk/core/auth/ext_app_authenticator.py +++ b/symphony/bdk/core/auth/ext_app_authenticator.py @@ -1,27 +1,30 @@ -"""Module containing extension app authenticator classes. -""" +"""Module containing extension app authenticator classes.""" + from abc import ABC, abstractmethod from symphony.bdk.core.auth.auth_session import AppAuthSession -from symphony.bdk.core.auth.jwt_helper import validate_jwt, create_signed_jwt -from symphony.bdk.core.auth.tokens_repository import TokensRepository, InMemoryTokensRepository +from symphony.bdk.core.auth.jwt_helper import create_signed_jwt, validate_jwt +from symphony.bdk.core.auth.tokens_repository import InMemoryTokensRepository, TokensRepository from symphony.bdk.core.config.model.bdk_retry_config import BdkRetryConfig from symphony.bdk.core.config.model.bdk_rsa_key_config import BdkRsaKeyConfig from symphony.bdk.core.retry import retry from symphony.bdk.core.retry.strategy import authentication_retry from symphony.bdk.gen.auth_api.certificate_authentication_api import CertificateAuthenticationApi from symphony.bdk.gen.auth_api.certificate_pod_api import CertificatePodApi -from symphony.bdk.gen.auth_model.extension_app_authenticate_request import ExtensionAppAuthenticateRequest +from symphony.bdk.gen.auth_model.extension_app_authenticate_request import ( + ExtensionAppAuthenticateRequest, +) from symphony.bdk.gen.login_api.authentication_api import AuthenticationApi -from symphony.bdk.gen.login_model.authenticate_extension_app_request import AuthenticateExtensionAppRequest +from symphony.bdk.gen.login_model.authenticate_extension_app_request import ( + AuthenticateExtensionAppRequest, +) from symphony.bdk.gen.login_model.extension_app_tokens import ExtensionAppTokens from symphony.bdk.gen.pod_api.pod_api import PodApi from symphony.bdk.gen.pod_model.pod_certificate import PodCertificate class ExtensionAppAuthenticator(ABC): - """Base abstract class to handle extension app authentication. - """ + """Base abstract class to handle extension app authentication.""" def __init__(self, app_id: str, tokens_repository: TokensRepository = None): """ @@ -93,16 +96,17 @@ async def _retrieve_tokens(self, app_token: str) -> ExtensionAppTokens: class ExtensionAppAuthenticatorRsa(ExtensionAppAuthenticator): - """A subclass of :class:`ExtensionAppAuthenticator` specific to extension app RSA authentication. - """ - - def __init__(self, - authentication_api: AuthenticationApi, - pod_api: PodApi, - app_id: str, - private_key_config: BdkRsaKeyConfig, - retry_config: BdkRetryConfig, - tokens_repository: TokensRepository = None): + """A subclass of :class:`ExtensionAppAuthenticator` specific to extension app RSA authentication.""" + + def __init__( + self, + authentication_api: AuthenticationApi, + pod_api: PodApi, + app_id: str, + private_key_config: BdkRsaKeyConfig, + retry_config: BdkRetryConfig, + tokens_repository: TokensRepository = None, + ): """ :param authentication_api: the AuthenticationApi instance @@ -122,9 +126,13 @@ def __init__(self, @retry(retry=authentication_retry) async def _retrieve_tokens(self, app_token: str) -> ExtensionAppTokens: jwt = create_signed_jwt(self._private_key_config, self._app_id) - authentication_request = AuthenticateExtensionAppRequest(app_token=app_token, auth_token=jwt) + authentication_request = AuthenticateExtensionAppRequest( + app_token=app_token, auth_token=jwt + ) - return await self._authentication_api.v1_pubkey_app_authenticate_extension_app_post(authentication_request) + return await self._authentication_api.v1_pubkey_app_authenticate_extension_app_post( + authentication_request + ) @retry(retry=authentication_retry) async def _get_pod_certificate(self) -> PodCertificate: @@ -132,15 +140,16 @@ async def _get_pod_certificate(self) -> PodCertificate: class ExtensionAppAuthenticatorCert(ExtensionAppAuthenticator): - """A subclass of :class:`ExtensionAppAuthenticator` specific to extension app certificate authentication. - """ - - def __init__(self, - certificate_authentication_api: CertificateAuthenticationApi, - certificate_pod_api: CertificatePodApi, - app_id: str, - retry_config: BdkRetryConfig, - tokens_repository: TokensRepository = None): + """A subclass of :class:`ExtensionAppAuthenticator` specific to extension app certificate authentication.""" + + def __init__( + self, + certificate_authentication_api: CertificateAuthenticationApi, + certificate_pod_api: CertificatePodApi, + app_id: str, + retry_config: BdkRetryConfig, + tokens_repository: TokensRepository = None, + ): """ :param certificate_authentication_api: the CertificateAuthenticationApi instance @@ -159,7 +168,8 @@ def __init__(self, async def _retrieve_tokens(self, app_token: str) -> ExtensionAppTokens: authentication_request = ExtensionAppAuthenticateRequest(app_token=app_token) return await self._certificate_authentication_api.v1_authenticate_extension_app_post( - auth_request=authentication_request) + auth_request=authentication_request + ) @retry(retry=authentication_retry) async def _get_pod_certificate(self) -> PodCertificate: diff --git a/symphony/bdk/core/auth/jwt_helper.py b/symphony/bdk/core/auth/jwt_helper.py index d4d7e94f..30782e9a 100644 --- a/symphony/bdk/core/auth/jwt_helper.py +++ b/symphony/bdk/core/auth/jwt_helper.py @@ -1,10 +1,10 @@ -"""Module to help with jwt handling. -""" +"""Module to help with jwt handling.""" + import datetime -from jwt import PyJWT, DecodeError, ExpiredSignatureError from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat from cryptography.x509 import load_pem_x509_certificate +from jwt import DecodeError, ExpiredSignatureError, PyJWT from symphony.bdk.core.auth.exception import AuthInitializationError from symphony.bdk.core.config.model.bdk_rsa_key_config import BdkRsaKeyConfig @@ -15,7 +15,10 @@ jwt = PyJWT({"verify_sub": False}) -def create_signed_jwt(private_key_config: BdkRsaKeyConfig, username: str, expiration: int = None) -> str: + +def create_signed_jwt( + private_key_config: BdkRsaKeyConfig, username: str, expiration: int = None +) -> str: """Creates a JWT with the provided user name and expiration date, signed with the provided private key. :param private_key_config: The private key configuration for a service account or an extension app. @@ -26,12 +29,14 @@ def create_signed_jwt(private_key_config: BdkRsaKeyConfig, username: str, expira :return: a signed JWT for a specific user or an extension app. """ - expiration = expiration if expiration is not None else int( - datetime.datetime.now(datetime.timezone.utc).timestamp() + DEFAULT_EXPIRATION_SECONDS) - payload = { - "sub": username, - "exp": expiration - } + expiration = ( + expiration + if expiration is not None + else int( + datetime.datetime.now(datetime.timezone.utc).timestamp() + DEFAULT_EXPIRATION_SECONDS + ) + ) + payload = {"sub": username, "exp": expiration} return create_signed_jwt_with_claims(private_key_config.get_private_key_content(), payload) @@ -56,8 +61,12 @@ def validate_jwt(jwt_token: str, certificate: str, allowed_audience: str) -> dic :raise AuthInitializationError: If the certificate or jwt are invalid. """ try: - return jwt.decode(jwt_token, _parse_public_key_from_x509_cert(certificate), - algorithms=[JWT_ENCRYPTION_ALGORITHM], audience=allowed_audience) + return jwt.decode( + jwt_token, + _parse_public_key_from_x509_cert(certificate), + algorithms=[JWT_ENCRYPTION_ALGORITHM], + audience=allowed_audience, + ) except (DecodeError, ExpiredSignatureError) as exc: raise AuthInitializationError("Unable to validate the jwt") from exc @@ -67,8 +76,7 @@ def generate_expiration_time() -> int: :return: timestamp value """ return int( - datetime.datetime.now(datetime.timezone.utc).timestamp() - + DEFAULT_EXPIRATION_SECONDS + datetime.datetime.now(datetime.timezone.utc).timestamp() + DEFAULT_EXPIRATION_SECONDS ) @@ -82,14 +90,17 @@ def _parse_public_key_from_x509_cert(certificate: str) -> str: public_key = load_pem_x509_certificate(certificate.encode()).public_key() return public_key.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo).decode() except ValueError as exc: - raise AuthInitializationError("Unable to parse the certificate. Check certificate format.") from exc + raise AuthInitializationError( + "Unable to parse the certificate. Check certificate format." + ) from exc def extract_token_claims(session_token): try: - return jwt.decode(session_token, - algorithms=[JWT_ENCRYPTION_ALGORITHM], - options={"verify_signature": False} - ) + return jwt.decode( + session_token, + algorithms=[JWT_ENCRYPTION_ALGORITHM], + options={"verify_signature": False}, + ) except DecodeError: return {} diff --git a/symphony/bdk/core/auth/obo_authenticator.py b/symphony/bdk/core/auth/obo_authenticator.py index a93f713f..9f12e8b3 100644 --- a/symphony/bdk/core/auth/obo_authenticator.py +++ b/symphony/bdk/core/auth/obo_authenticator.py @@ -1,5 +1,5 @@ -"""Module containing OBO authenticator classes. -""" +"""Module containing OBO authenticator classes.""" + from abc import ABC, abstractmethod from symphony.bdk.core.auth.auth_session import OboAuthSession @@ -14,11 +14,9 @@ class OboAuthenticator(ABC): - """Obo authentication service. - """ + """Obo authentication service.""" - unauthorized_message = \ - "Extension Application is not authorized to authenticate in OBO mode. Check if credentials are valid." + unauthorized_message = "Extension Application is not authorized to authenticate in OBO mode. Check if credentials are valid." @abstractmethod async def retrieve_obo_session_token_by_user_id(self, user_id: int) -> str: @@ -56,10 +54,14 @@ def authenticate_by_user_id(self, user_id: int) -> OboAuthSession: class OboAuthenticatorRsa(OboAuthenticator): - """Obo authenticator RSA implementation. - """ - - def __init__(self, app_config: BdkAppConfig, authentication_api: AuthenticationApi, retry_config: BdkRetryConfig): + """Obo authenticator RSA implementation.""" + + def __init__( + self, + app_config: BdkAppConfig, + authentication_api: AuthenticationApi, + retry_config: BdkRetryConfig, + ): self._app_config = app_config self._authentication_api = authentication_api self._retry_config = retry_config @@ -95,21 +97,26 @@ async def _authenticate_and_retrieve_app_session_token(self) -> str: @retry(retry=authentication_retry) async def _authenticate_by_user_id(self, app_session_token, user_id) -> str: token = await self._authentication_api.pubkey_app_user_user_id_authenticate_post( - session_token=app_session_token, user_id=user_id) + session_token=app_session_token, user_id=user_id + ) return token.token @retry(retry=authentication_retry) async def _authenticate_by_username(self, app_session_token, username) -> str: token = await self._authentication_api.pubkey_app_username_username_authenticate_post( - session_token=app_session_token, username=username) + session_token=app_session_token, username=username + ) return token.token class OboAuthenticatorCert(OboAuthenticator): - """Obo authenticator Certificate implementation. - """ + """Obo authenticator Certificate implementation.""" - def __init__(self, certificate_authenticator_api: CertificateAuthenticationApi, retry_config: BdkRetryConfig): + def __init__( + self, + certificate_authenticator_api: CertificateAuthenticationApi, + retry_config: BdkRetryConfig, + ): self._authentication_api = certificate_authenticator_api self._retry_config = retry_config @@ -122,8 +129,9 @@ async def retrieve_obo_session_token_by_user_id(self, user_id: int) -> str: :raise AuthUnauthorizedError: if session token cannot be retrieved """ app_session_token = await self._retrieve_app_session_token() - obo_auth = await self._authentication_api.v1_app_user_uid_authenticate_post(session_token=app_session_token, - uid=user_id) + obo_auth = await self._authentication_api.v1_app_user_uid_authenticate_post( + session_token=app_session_token, uid=user_id + ) return obo_auth.session_token @retry(retry=authentication_retry) @@ -136,7 +144,8 @@ async def retrieve_obo_session_token_by_username(self, username: str) -> str: """ app_session_token = await self._retrieve_app_session_token() obo_auth = await self._authentication_api.v1_app_username_username_authenticate_post( - session_token=app_session_token, username=username) + session_token=app_session_token, username=username + ) return obo_auth.session_token @retry(retry=authentication_retry) diff --git a/symphony/bdk/core/auth/tokens_repository.py b/symphony/bdk/core/auth/tokens_repository.py index 2b8b669f..c8a4fa33 100644 --- a/symphony/bdk/core/auth/tokens_repository.py +++ b/symphony/bdk/core/auth/tokens_repository.py @@ -1,11 +1,10 @@ -"""Module which handles the storage of valid extension app tokens. -""" +"""Module which handles the storage of valid extension app tokens.""" + from abc import ABC, abstractmethod class TokensRepository(ABC): - """Base abstract class to store and retrieve extension app tokens. - """ + """Base abstract class to store and retrieve extension app tokens.""" @abstractmethod async def save(self, app_token: str, symphony_token: str) -> None: @@ -26,8 +25,8 @@ async def get(self, app_token: str) -> str: class InMemoryTokensRepository(TokensRepository): - """Class implementing an in-memory TokensRepository. - """ + """Class implementing an in-memory TokensRepository.""" + def __init__(self): self._tokens = {} diff --git a/symphony/bdk/core/client/__init__.py b/symphony/bdk/core/client/__init__.py index 483ac588..d6ed63e2 100644 --- a/symphony/bdk/core/client/__init__.py +++ b/symphony/bdk/core/client/__init__.py @@ -1,5 +1,5 @@ -"""Package for instantiating internal API clients -""" +"""Package for instantiating internal API clients""" + from symphony.bdk.core.client.trace_id import setup_trace_id_log_record_factory setup_trace_id_log_record_factory() diff --git a/symphony/bdk/core/client/api_client_factory.py b/symphony/bdk/core/client/api_client_factory.py index 90cb133b..c5ad853a 100644 --- a/symphony/bdk/core/client/api_client_factory.py +++ b/symphony/bdk/core/client/api_client_factory.py @@ -1,14 +1,14 @@ -"""Module containing the ApiClientFactory class. -""" +"""Module containing the ApiClientFactory class.""" + import logging import sys -from importlib.metadata import distribution, PackageNotFoundError +from importlib.metadata import PackageNotFoundError, distribution from ssl import SSLError import urllib3 from aiohttp.hdrs import USER_AGENT -from symphony.bdk.core.client.trace_id import add_x_trace_id, X_TRACE_ID +from symphony.bdk.core.client.trace_id import X_TRACE_ID, add_x_trace_id from symphony.bdk.gen.api_client import ApiClient from symphony.bdk.gen.configuration import Configuration @@ -23,8 +23,7 @@ class ApiClientFactory: - """Factory responsible for creating ApiClient instances for each main Symphony's components. - """ + """Factory responsible for creating ApiClient instances for each main Symphony's components.""" def __init__(self, config): self._config = config @@ -32,12 +31,15 @@ def __init__(self, config): self._pod_client = self._get_api_client(self._config.pod, POD) self._relay_client = self._get_api_client(self._config.key_manager, RELAY) self._agent_client = self._get_api_client(self._config.agent, AGENT) - self._session_auth_client = self._get_api_client_with_client_cert(self._config.session_auth, SESSION_AUTH, - self._config.bot.certificate.path) - self._key_auth_client = self._get_api_client_with_client_cert(self._config.key_manager, KEY_AUTH, - self._config.bot.certificate.path) - self._app_session_auth_client = self._get_api_client_with_client_cert(self._config.session_auth, SESSION_AUTH, - self._config.app.certificate.path) + self._session_auth_client = self._get_api_client_with_client_cert( + self._config.session_auth, SESSION_AUTH, self._config.bot.certificate.path + ) + self._key_auth_client = self._get_api_client_with_client_cert( + self._config.key_manager, KEY_AUTH, self._config.bot.certificate.path + ) + self._app_session_auth_client = self._get_api_client_with_client_cert( + self._config.session_auth, SESSION_AUTH, self._config.app.certificate.path + ) self._custom_clients = [] def get_login_client(self) -> ApiClient: @@ -117,14 +119,18 @@ def _get_api_client(self, server_config, context) -> ApiClient: configuration = self._get_client_config(context, server_config) return ApiClientFactory._get_api_client_from_config(configuration, server_config) - def _get_api_client_with_client_cert(self, server_config, context, certificate_path) -> ApiClient: + def _get_api_client_with_client_cert( + self, server_config, context, certificate_path + ) -> ApiClient: configuration = self._get_client_config(context, server_config) configuration.cert_file = certificate_path return ApiClientFactory._get_api_client_from_config(configuration, server_config) def _get_client_config(self, context, server_config): - configuration = Configuration(host=(server_config.get_base_path() + context), discard_unknown_keys=True) + configuration = Configuration( + host=(server_config.get_base_path() + context), discard_unknown_keys=True + ) configuration.verify_ssl = True configuration.ssl_ca_cert = self._config.ssl.trust_store_path if server_config.proxy is not None: @@ -138,7 +144,9 @@ def _get_api_client_from_config(client_config, server_config): ApiClientFactory._add_headers(client, server_config) return client except SSLError as exc: - logger.exception("SSL error when instantiating clients, please check certificates are valid") + logger.exception( + "SSL error when instantiating clients, please check certificates are valid" + ) raise exc @staticmethod @@ -149,7 +157,9 @@ def _add_headers(client, server_config): client.set_default_header(header_name, header_value) client.user_agent = ApiClientFactory._user_agent(server_config) - if X_TRACE_ID.lower() not in (header_name.lower() for header_name in default_headers.keys()): + if X_TRACE_ID.lower() not in ( + header_name.lower() for header_name in default_headers.keys() + ): client._ApiClient__call_api = add_x_trace_id(client._ApiClient__call_api) @staticmethod @@ -159,23 +169,31 @@ def _configure_proxy(server_config, configuration): configuration.proxy = proxy_config.get_url() if proxy_config.are_credentials_defined(): - configuration.proxy_headers = urllib3.util.make_headers(proxy_basic_auth=proxy_config.get_credentials(), - user_agent=user_agent) + configuration.proxy_headers = urllib3.util.make_headers( + proxy_basic_auth=proxy_config.get_credentials(), user_agent=user_agent + ) else: configuration.proxy_headers = urllib3.util.make_headers(user_agent=user_agent) @staticmethod def _sanitized_default_headers(server_config): - default_headers = server_config.default_headers if server_config.default_headers is not None else {} + default_headers = ( + server_config.default_headers if server_config.default_headers is not None else {} + ) # we do this because we want to handle user-agent header separately # for client configuration and proxy header - return {header_key: default_headers[header_key] for header_key in default_headers - if header_key.lower() != USER_AGENT.lower()} + return { + header_key: default_headers[header_key] + for header_key in default_headers + if header_key.lower() != USER_AGENT.lower() + } @staticmethod def _user_agent(server_config): - default_headers = server_config.default_headers if server_config.default_headers is not None else {} + default_headers = ( + server_config.default_headers if server_config.default_headers is not None else {} + ) for header_key, header_value in default_headers.items(): if header_key.lower() == USER_AGENT.lower(): diff --git a/symphony/bdk/core/client/trace_id.py b/symphony/bdk/core/client/trace_id.py index 8cd32ff0..12f27d48 100644 --- a/symphony/bdk/core/client/trace_id.py +++ b/symphony/bdk/core/client/trace_id.py @@ -1,5 +1,5 @@ -"""Module responsible for managing the trace ID sent as X-Trace-Id header and logged under the `trace_id` variable -""" +"""Module responsible for managing the trace ID sent as X-Trace-Id header and logged under the `trace_id` variable""" + import logging import random import string @@ -49,8 +49,7 @@ async def add_x_trace_id_header(*args, **kwargs): class DistributedTracingContext: - """Class to manage the tracing id context. - """ + """Class to manage the tracing id context.""" _trace_id_context = ContextVar("_trace_id_context") _is_trace_id_set_by_user = False diff --git a/symphony/bdk/core/config/__init__.py b/symphony/bdk/core/config/__init__.py index 73e5663f..b8aba153 100644 --- a/symphony/bdk/core/config/__init__.py +++ b/symphony/bdk/core/config/__init__.py @@ -1,2 +1 @@ -"""Package containing configuration-related modules -""" +"""Package containing configuration-related modules""" diff --git a/symphony/bdk/core/config/exception.py b/symphony/bdk/core/config/exception.py index ccbaa1ab..c4d6cca2 100644 --- a/symphony/bdk/core/config/exception.py +++ b/symphony/bdk/core/config/exception.py @@ -1,10 +1,8 @@ -"""Module containing all configuration related exception. -""" +"""Module containing all configuration related exception.""" class BdkConfigError(Exception): - """Exception class raised when configuration is invalid. - """ + """Exception class raised when configuration is invalid.""" class BotNotConfiguredError(Exception): diff --git a/symphony/bdk/core/config/loader.py b/symphony/bdk/core/config/loader.py index 941b892d..5c6e5a10 100644 --- a/symphony/bdk/core/config/loader.py +++ b/symphony/bdk/core/config/loader.py @@ -8,7 +8,7 @@ class BdkConfigLoader: - """ Config loader class + """Config loader class Provide methods to load a JSON or YAML configuration from an absolute path or `$HOME/.symphony`` @@ -54,7 +54,7 @@ def load_from_symphony_dir(cls, relative_path: str) -> BdkConfig: class BdkConfigParser: - """ Config Parser class + """Config Parser class Provide methods to Deserialize a configuration content as a ``str`` in a JSON or YAML format to a BdkConfig object. diff --git a/symphony/bdk/core/config/model/bdk_app_config.py b/symphony/bdk/core/config/model/bdk_app_config.py index fd4b56d5..ac6814e8 100644 --- a/symphony/bdk/core/config/model/bdk_app_config.py +++ b/symphony/bdk/core/config/model/bdk_app_config.py @@ -2,12 +2,14 @@ class BdkAppConfig(BdkAuthenticationConfig): - """Class containing the extension app configuration - """ + """Class containing the extension app configuration""" def __init__(self, config): if config is not None: self.app_id = config.get("appId") - super().__init__(private_key_config=config.get("privateKey"), certificate_config=config.get("certificate")) + super().__init__( + private_key_config=config.get("privateKey"), + certificate_config=config.get("certificate"), + ) else: super().__init__() diff --git a/symphony/bdk/core/config/model/bdk_authentication_config.py b/symphony/bdk/core/config/model/bdk_authentication_config.py index 065bafbf..06b08a86 100644 --- a/symphony/bdk/core/config/model/bdk_authentication_config.py +++ b/symphony/bdk/core/config/model/bdk_authentication_config.py @@ -1,7 +1,7 @@ -"""Module handling authentication part of the configuration. -""" -from symphony.bdk.core.config.model.bdk_rsa_key_config import BdkRsaKeyConfig +"""Module handling authentication part of the configuration.""" + from symphony.bdk.core.config.model.bdk_certificate_config import BdkCertificateConfig +from symphony.bdk.core.config.model.bdk_rsa_key_config import BdkRsaKeyConfig class BdkAuthenticationConfig: @@ -11,10 +11,16 @@ class BdkAuthenticationConfig: """ def __init__(self, private_key_config=None, certificate_config=None): - self.private_key = BdkRsaKeyConfig( - **private_key_config) if private_key_config is not None else BdkRsaKeyConfig() - self.certificate = BdkCertificateConfig( - **certificate_config) if certificate_config is not None else BdkCertificateConfig() + self.private_key = ( + BdkRsaKeyConfig(**private_key_config) + if private_key_config is not None + else BdkRsaKeyConfig() + ) + self.certificate = ( + BdkCertificateConfig(**certificate_config) + if certificate_config is not None + else BdkCertificateConfig() + ) def is_authentication_configured(self) -> bool: """Check if either RSA or certificate authentication is configured diff --git a/symphony/bdk/core/config/model/bdk_bot_config.py b/symphony/bdk/core/config/model/bdk_bot_config.py index df575f99..3023176b 100644 --- a/symphony/bdk/core/config/model/bdk_bot_config.py +++ b/symphony/bdk/core/config/model/bdk_bot_config.py @@ -2,11 +2,14 @@ class BdkBotConfig(BdkAuthenticationConfig): - """Class containing the bot configuration - """ + """Class containing the bot configuration""" + def __init__(self, config): if config is not None: self.username = config.get("username") - super().__init__(private_key_config=config.get("privateKey"), certificate_config=config.get("certificate")) + super().__init__( + private_key_config=config.get("privateKey"), + certificate_config=config.get("certificate"), + ) else: super().__init__() diff --git a/symphony/bdk/core/config/model/bdk_client_config.py b/symphony/bdk/core/config/model/bdk_client_config.py index d0dffe14..16ace3cd 100644 --- a/symphony/bdk/core/config/model/bdk_client_config.py +++ b/symphony/bdk/core/config/model/bdk_client_config.py @@ -1,4 +1,4 @@ -from symphony.bdk.core.config.model.bdk_server_config import BdkServerConfig, BdkProxyConfig +from symphony.bdk.core.config.model.bdk_server_config import BdkProxyConfig, BdkServerConfig class BdkClientConfig(BdkServerConfig): diff --git a/symphony/bdk/core/config/model/bdk_config.py b/symphony/bdk/core/config/model/bdk_config.py index ee392bfc..626d6b9f 100644 --- a/symphony/bdk/core/config/model/bdk_config.py +++ b/symphony/bdk/core/config/model/bdk_config.py @@ -1,25 +1,29 @@ from symphony.bdk.core.config.model.bdk_app_config import BdkAppConfig from symphony.bdk.core.config.model.bdk_bot_config import BdkBotConfig from symphony.bdk.core.config.model.bdk_client_config import BdkClientConfig +from symphony.bdk.core.config.model.bdk_datafeed_config import BdkDatafeedConfig +from symphony.bdk.core.config.model.bdk_datahose_config import BdkDatahoseConfig from symphony.bdk.core.config.model.bdk_retry_config import BdkRetryConfig from symphony.bdk.core.config.model.bdk_server_config import BdkServerConfig from symphony.bdk.core.config.model.bdk_ssl_config import BdkSslConfig -from symphony.bdk.core.config.model.bdk_datafeed_config import BdkDatafeedConfig -from symphony.bdk.core.config.model.bdk_datahose_config import BdkDatahoseConfig class BdkConfig(BdkServerConfig): - """Class containing the whole BDK configuration. - """ + """Class containing the whole BDK configuration.""" def __init__(self, **config): """ :param config: the dict containing the server configuration parameters. """ - super().__init__(scheme=config.get("scheme"), host=config.get("host"), port=config.get("port"), - context=config.get("context"), proxy=config.get("proxy"), - default_headers=config.get("defaultHeaders")) + super().__init__( + scheme=config.get("scheme"), + host=config.get("host"), + port=config.get("port"), + context=config.get("context"), + proxy=config.get("proxy"), + default_headers=config.get("defaultHeaders"), + ) self.agent = BdkClientConfig(self, config.get("agent")) self.pod = BdkClientConfig(self, config.get("pod")) self.key_manager = BdkClientConfig(self, config.get("keyManager")) diff --git a/symphony/bdk/core/config/model/bdk_datafeed_config.py b/symphony/bdk/core/config/model/bdk_datafeed_config.py index d3c3d628..4e84760c 100644 --- a/symphony/bdk/core/config/model/bdk_datafeed_config.py +++ b/symphony/bdk/core/config/model/bdk_datafeed_config.py @@ -10,19 +10,18 @@ def log_dfv1_deprecation(version): - """Logs a warning message when datafeed v1 is used in the bot configuration - """ + """Logs a warning message when datafeed v1 is used in the bot configuration""" if version is not None and version.lower() == DF_V1: logging.warning( "The datafeed 1 service will be fully replaced by the datafeed 2 service in the future. " "Please consider migrating over to datafeed 2. For more information on the timeline as well as on " "the benefits of datafeed 2, please reach out to your Technical Account Manager or to our developer " - "documentation https://docs.developers.symphony.com/building-bots-on-symphony/datafeed)") + "documentation https://docs.developers.symphony.com/building-bots-on-symphony/datafeed)" + ) class BdkDatafeedConfig: - """Class holding datafeed specific configuration. - """ + """Class holding datafeed specific configuration.""" def __init__(self, config): """ @@ -33,7 +32,9 @@ def __init__(self, config): self.id_file_path = "" self.retry = BdkRetryConfig(dict(maxAttempts=BdkRetryConfig.INFINITE_MAX_ATTEMPTS)) if config is not None: - self.id_file_path = Path(config.get(DF_ID_FILE_PATH)) if DF_ID_FILE_PATH in config else "" + self.id_file_path = ( + Path(config.get(DF_ID_FILE_PATH)) if DF_ID_FILE_PATH in config else "" + ) log_dfv1_deprecation(config.get(VERSION)) self.version = config.get(VERSION) if "retry" in config: diff --git a/symphony/bdk/core/config/model/bdk_datahose_config.py b/symphony/bdk/core/config/model/bdk_datahose_config.py index 5911cdc6..93b36835 100644 --- a/symphony/bdk/core/config/model/bdk_datahose_config.py +++ b/symphony/bdk/core/config/model/bdk_datahose_config.py @@ -6,8 +6,7 @@ class BdkDatahoseConfig: - """Class holding datahose specific configuration. - """ + """Class holding datahose specific configuration.""" def __init__(self, config): """ diff --git a/symphony/bdk/core/config/model/bdk_retry_config.py b/symphony/bdk/core/config/model/bdk_retry_config.py index 9fdc0318..dcb7d53f 100644 --- a/symphony/bdk/core/config/model/bdk_retry_config.py +++ b/symphony/bdk/core/config/model/bdk_retry_config.py @@ -4,6 +4,7 @@ class BdkRetryConfig: """Class containing the retry configuration""" + INFINITE_MAX_ATTEMPTS = -1 DEFAULT_MAX_ATTEMPTS = 10 DEFAULT_INITIAL_INTERVAL = 500 @@ -17,9 +18,12 @@ def __init__(self, config=None): self.max_attempts = config.get("maxAttempts", self.DEFAULT_MAX_ATTEMPTS) if self.max_attempts < 0: self.max_attempts = sys.maxsize - self.initial_interval = timedelta(milliseconds=config.get("initialIntervalMillis", - self.DEFAULT_INITIAL_INTERVAL)) + self.initial_interval = timedelta( + milliseconds=config.get("initialIntervalMillis", self.DEFAULT_INITIAL_INTERVAL) + ) self.multiplier = config.get("multiplier", self.DEFAULT_MULTIPLIER) if self.multiplier < 1: self.multiplier = self.DEFAULT_MULTIPLIER - self.max_interval = timedelta(milliseconds=config.get("maxIntervalMillis", self.DEFAULT_MAX_INTERVAL)) + self.max_interval = timedelta( + milliseconds=config.get("maxIntervalMillis", self.DEFAULT_MAX_INTERVAL) + ) diff --git a/symphony/bdk/core/config/model/bdk_rsa_key_config.py b/symphony/bdk/core/config/model/bdk_rsa_key_config.py index 3da087c2..73fdce77 100644 --- a/symphony/bdk/core/config/model/bdk_rsa_key_config.py +++ b/symphony/bdk/core/config/model/bdk_rsa_key_config.py @@ -1,6 +1,6 @@ class BdkRsaKeyConfig: - """Class containing the bot's RSA Key configuration - """ + """Class containing the bot's RSA Key configuration""" + def __init__(self, path=None, content=""): self._path = path self._content = content @@ -33,7 +33,7 @@ def path(self, rsa_key_path): self._content = None def is_configured(self) -> bool: - """"Check if the RSA authentication is configured or not + """ "Check if the RSA authentication is configured or not :return: true if the RSA authentication is configured """ @@ -53,8 +53,7 @@ def get_private_key_content(self) -> str: :return: private key content as string. """ - return self._load_key_from_path() \ - if self._path is not None else self._content + return self._load_key_from_path() if self._path is not None else self._content def _load_key_from_path(self): with open(self._path, "r") as file: diff --git a/symphony/bdk/core/config/model/bdk_server_config.py b/symphony/bdk/core/config/model/bdk_server_config.py index 61e95ebc..c1e2d7de 100644 --- a/symphony/bdk/core/config/model/bdk_server_config.py +++ b/symphony/bdk/core/config/model/bdk_server_config.py @@ -1,9 +1,12 @@ class BdkServerConfig: """Base class for server and client configurations""" + DEFAULT_SCHEME: str = "https" DEFAULT_HTTPS_PORT: int = 443 - def __init__(self, scheme=None, port=None, context="", host=None, proxy=None, default_headers=None): + def __init__( + self, scheme=None, port=None, context="", host=None, proxy=None, default_headers=None + ): self.scheme = scheme if scheme is not None else self.DEFAULT_SCHEME self.port = port if port is not None else self.DEFAULT_HTTPS_PORT self.context = context @@ -17,7 +20,9 @@ def get_base_path(self) -> str: :return: scheme://host:port + formatted_context :rtype: str """ - return f"{self.scheme}://{self.host}{self.get_port_as_string()}{self.get_formatted_context()}" + return ( + f"{self.scheme}://{self.host}{self.get_port_as_string()}{self.get_formatted_context()}" + ) def get_formatted_context(self) -> str: """Formats the context field diff --git a/symphony/bdk/core/extension.py b/symphony/bdk/core/extension.py index 9b1c4549..dd04d539 100644 --- a/symphony/bdk/core/extension.py +++ b/symphony/bdk/core/extension.py @@ -1,5 +1,5 @@ -"""Module for managing extensions. -""" +"""Module for managing extensions.""" + import logging from abc import ABC, abstractmethod from typing import Union @@ -12,8 +12,8 @@ class BdkConfigAware(ABC): - """Interface that extensions need to implement to have the config injected - """ + """Interface that extensions need to implement to have the config injected""" + @abstractmethod def set_config(self, bdk_config: BdkConfig): """Method that is called by the :class:`ExtensionService` to inject the BDK configuration. @@ -24,8 +24,8 @@ def set_config(self, bdk_config: BdkConfig): class BdkAuthenticationAware(ABC): - """Interface that extensions need to implement to have the bot session injected - """ + """Interface that extensions need to implement to have the bot session injected""" + @abstractmethod def set_bot_session(self, auth_session: AuthSession): """Method that is called by the :class:`ExtensionService` to inject the BDK auth session. @@ -36,8 +36,8 @@ def set_bot_session(self, auth_session: AuthSession): class BdkApiClientFactoryAware(ABC): - """Interface that extensions need to implement to have the api client factory injected - """ + """Interface that extensions need to implement to have the api client factory injected""" + @abstractmethod def set_api_client_factory(self, api_client_factory: ApiClientFactory): """Method that is called by the :class:`ExtensionService` to inject the BDK api client factory. @@ -48,8 +48,8 @@ def set_api_client_factory(self, api_client_factory: ApiClientFactory): class BdkExtensionServiceProvider(ABC): - """Interface that extensions need to implement to expose a service - """ + """Interface that extensions need to implement to expose a service""" + @abstractmethod def get_service(self): """Method that is called by the :func:`~ExtensionService.service` @@ -59,8 +59,8 @@ def get_service(self): class ExtensionService: - """Service class for managing extensions - """ + """Service class for managing extensions""" + def __init__(self, api_client_factory, bot_session, config): self._api_client_factory = api_client_factory self._bot_session = bot_session @@ -102,7 +102,9 @@ def service(self, extension_type: type): try: return extension.get_service() except AttributeError: - raise ValueError(f"Extension {str(extension_type)} does not implement the get_service method") + raise ValueError( + f"Extension {str(extension_type)} does not implement the get_service method" + ) def _inject_api_client_factory(self, extension): try: diff --git a/symphony/bdk/core/retry/__init__.py b/symphony/bdk/core/retry/__init__.py index 8c965c20..75339705 100644 --- a/symphony/bdk/core/retry/__init__.py +++ b/symphony/bdk/core/retry/__init__.py @@ -1,8 +1,8 @@ import logging -from typing import Callable from functools import wraps +from typing import Callable -from tenacity import stop_after_attempt, wait_exponential, before_sleep_log +from tenacity import before_sleep_log, stop_after_attempt, wait_exponential from symphony.bdk.core.config.model.bdk_retry_config import BdkRetryConfig from symphony.bdk.core.retry.strategy import refresh_session_if_unauthorized @@ -31,17 +31,21 @@ def decorator_f(self, *args, **kwargs): arguments for the AsyncRetrying object. """ default_kwargs = {} - retry_config: BdkRetryConfig = getattr(self, '_retry_config') + retry_config: BdkRetryConfig = getattr(self, "_retry_config") logger = logging.getLogger(self.__module__) _before_sleep = before_sleep_log(logger, logging.INFO) default_kwargs.update(dict(before_sleep=_before_sleep)) if retry_config is not None: - config_kwargs = dict(retry=retry_function, - wait=wait_exponential(multiplier=retry_config.multiplier, - min=retry_config.initial_interval.total_seconds(), - max=retry_config.max_interval.total_seconds()), - stop=stop_after_attempt(retry_config.max_attempts), - reraise=True) + config_kwargs = dict( + retry=retry_function, + wait=wait_exponential( + multiplier=retry_config.multiplier, + min=retry_config.initial_interval.total_seconds(), + max=retry_config.max_interval.total_seconds(), + ), + stop=stop_after_attempt(retry_config.max_attempts), + reraise=True, + ) default_kwargs.update(config_kwargs) # update default arguments by the ones passed as parameters default_kwargs.update(**dkw) diff --git a/symphony/bdk/core/retry/_asyncio.py b/symphony/bdk/core/retry/_asyncio.py index fad6acf4..7c0acadb 100644 --- a/symphony/bdk/core/retry/_asyncio.py +++ b/symphony/bdk/core/retry/_asyncio.py @@ -7,14 +7,16 @@ import sys - import six - -from tenacity import AttemptManager, RetryAction, TryAgain -from tenacity import BaseRetrying -from tenacity import DoAttempt -from tenacity import DoSleep -from tenacity import RetryCallState +from tenacity import ( + AttemptManager, + BaseRetrying, + DoAttempt, + DoSleep, + RetryAction, + RetryCallState, + TryAgain, +) class AsyncRetrying(BaseRetrying): @@ -23,6 +25,7 @@ class AsyncRetrying(BaseRetrying): There is an open PR for this change on the tenacity repository: https://github.com/jd/tenacity/pull/289 """ + def __init__(self, sleep=asyncio.sleep, **kwargs): super().__init__(**kwargs) self.sleep = sleep diff --git a/symphony/bdk/core/retry/strategy.py b/symphony/bdk/core/retry/strategy.py index 8460889e..2fa56670 100644 --- a/symphony/bdk/core/retry/strategy.py +++ b/symphony/bdk/core/retry/strategy.py @@ -1,5 +1,6 @@ -from aiohttp import ClientConnectionError from asyncio import TimeoutError + +from aiohttp import ClientConnectionError from tenacity import RetryCallState from symphony.bdk.core.auth.exception import AuthUnauthorizedError @@ -111,7 +112,9 @@ async def read_datafeed_retry(retry_state: RetryCallState): exception = retry_state.outcome.exception() if is_network_or_minor_error_or_client(exception): if is_client_error(exception): - datafeed_service = retry_state.args[0] # datafeed_service is an AbstractDataFeedLoop instance + datafeed_service = retry_state.args[ + 0 + ] # datafeed_service is an AbstractDataFeedLoop instance await datafeed_service.recreate_datafeed() elif is_unauthorized(exception): service_auth_session = retry_state.args[0]._auth_session diff --git a/symphony/bdk/core/service/__init__.py b/symphony/bdk/core/service/__init__.py index 67856dcc..3712b47e 100644 --- a/symphony/bdk/core/service/__init__.py +++ b/symphony/bdk/core/service/__init__.py @@ -1,2 +1 @@ -"""Package containing modules and packages related to services (message, stream, user, etc.) -""" +"""Package containing modules and packages related to services (message, stream, user, etc.)""" diff --git a/symphony/bdk/core/service/application/application_service.py b/symphony/bdk/core/service/application/application_service.py index 9eecc6b2..7de60a3e 100644 --- a/symphony/bdk/core/service/application/application_service.py +++ b/symphony/bdk/core/service/application/application_service.py @@ -26,8 +26,13 @@ class ApplicationService: * Update user applications """ - def __init__(self, application_api: ApplicationApi, app_entitlement_api: AppEntitlementApi, - auth_session: AuthSession, retry_config: BdkRetryConfig): + def __init__( + self, + application_api: ApplicationApi, + app_entitlement_api: AppEntitlementApi, + auth_session: AuthSession, + retry_config: BdkRetryConfig, + ): self._application_api = application_api self._app_entitlement_api = app_entitlement_api self._auth_session = auth_session @@ -50,13 +55,15 @@ async def create_application(self, application_detail: ApplicationDetail) -> App """ params = { - 'application_detail': application_detail, - 'session_token': await self._auth_session.session_token + "application_detail": application_detail, + "session_token": await self._auth_session.session_token, } return await self._application_api.v1_admin_app_create_post(**params) @retry - async def update_application(self, app_id: str, application_detail: ApplicationDetail) -> ApplicationDetail: + async def update_application( + self, app_id: str, application_detail: ApplicationDetail + ) -> ApplicationDetail: """ Update an existing application. @@ -73,9 +80,9 @@ async def update_application(self, app_id: str, application_detail: ApplicationD """ params = { - 'id': app_id, - 'application_detail': application_detail, - 'session_token': await self._auth_session.session_token + "id": app_id, + "application_detail": application_detail, + "session_token": await self._auth_session.session_token, } return await self._application_api.v1_admin_app_id_update_post(**params) @@ -89,10 +96,7 @@ async def delete_application(self, app_id: str) -> None: :param app_id: Id of the application needs to be deleted. """ - params = { - 'id': app_id, - 'session_token': await self._auth_session.session_token - } + params = {"id": app_id, "session_token": await self._auth_session.session_token} await self._application_api.v1_admin_app_id_delete_post(**params) @retry @@ -107,10 +111,7 @@ async def get_application(self, app_id: str) -> ApplicationDetail: :return: The detail of the lookup application. """ - params = { - 'id': app_id, - 'session_token': await self._auth_session.session_token - } + params = {"id": app_id, "session_token": await self._auth_session.session_token} return await self._application_api.v1_admin_app_id_get_get(**params) @retry @@ -123,14 +124,16 @@ async def list_application_entitlements(self) -> [PodAppEntitlement]: :return: The list of application entitlements. """ - params = { - 'session_token': await self._auth_session.session_token - } - pod_app_entitlement_list = await self._app_entitlement_api.v1_admin_app_entitlement_list_get(**params) + params = {"session_token": await self._auth_session.session_token} + pod_app_entitlement_list = ( + await self._app_entitlement_api.v1_admin_app_entitlement_list_get(**params) + ) return pod_app_entitlement_list.value @retry - async def update_application_entitlements(self, entitlements: [PodAppEntitlement]) -> [PodAppEntitlement]: + async def update_application_entitlements( + self, entitlements: [PodAppEntitlement] + ) -> [PodAppEntitlement]: """ Update the list of application entitlements for the company. @@ -142,10 +145,12 @@ async def update_application_entitlements(self, entitlements: [PodAppEntitlement """ params = { - 'payload': PodAppEntitlementList(value=entitlements), - 'session_token': await self._auth_session.session_token + "payload": PodAppEntitlementList(value=entitlements), + "session_token": await self._auth_session.session_token, } - pod_app_entitlement_list = await self._app_entitlement_api.v1_admin_app_entitlement_list_post(**params) + pod_app_entitlement_list = ( + await self._app_entitlement_api.v1_admin_app_entitlement_list_post(**params) + ) return pod_app_entitlement_list.value @retry @@ -160,15 +165,16 @@ async def list_user_applications(self, user_id: int) -> [UserAppEntitlement]: :return: The list of Symphony application entitlements for this user. """ - params = { - 'uid': user_id, - 'session_token': await self._auth_session.session_token - } - user_app_entitlement_list = await self._app_entitlement_api.v1_admin_user_uid_app_entitlement_list_get(**params) + params = {"uid": user_id, "session_token": await self._auth_session.session_token} + user_app_entitlement_list = ( + await self._app_entitlement_api.v1_admin_user_uid_app_entitlement_list_get(**params) + ) return user_app_entitlement_list.value @retry - async def update_user_applications(self, user_id: int, user_app_entitlements: [UserAppEntitlement]): + async def update_user_applications( + self, user_id: int, user_app_entitlements: [UserAppEntitlement] + ): """ Update the application entitlements for a particular user. @@ -181,16 +187,19 @@ async def update_user_applications(self, user_id: int, user_app_entitlements: [U """ params = { - 'uid': user_id, - 'payload': UserAppEntitlementList(value=user_app_entitlements), - 'session_token': await self._auth_session.session_token + "uid": user_id, + "payload": UserAppEntitlementList(value=user_app_entitlements), + "session_token": await self._auth_session.session_token, } - user_app_entitlement_list \ - = await self._app_entitlement_api.v1_admin_user_uid_app_entitlement_list_post(**params) + user_app_entitlement_list = ( + await self._app_entitlement_api.v1_admin_user_uid_app_entitlement_list_post(**params) + ) return user_app_entitlement_list.value @retry - async def patch_user_applications(self, user_id: int, user_app_entitlements: [UserAppEntitlementPatch]): + async def patch_user_applications( + self, user_id: int, user_app_entitlements: [UserAppEntitlementPatch] + ): """ Updates particular app entitlements for a particular user. Supports partial update. @@ -203,10 +212,11 @@ async def patch_user_applications(self, user_id: int, user_app_entitlements: [Us """ params = { - 'uid': user_id, - 'payload': UserAppEntitlementsPatchList(value=user_app_entitlements), - 'session_token': await self._auth_session.session_token + "uid": user_id, + "payload": UserAppEntitlementsPatchList(value=user_app_entitlements), + "session_token": await self._auth_session.session_token, } - user_app_entitlements_list \ - = await self._app_entitlement_api.v1_admin_user_uid_app_entitlement_list_patch(**params) + user_app_entitlements_list = ( + await self._app_entitlement_api.v1_admin_user_uid_app_entitlement_list_patch(**params) + ) return user_app_entitlements_list.value diff --git a/symphony/bdk/core/service/connection/connection_service.py b/symphony/bdk/core/service/connection/connection_service.py index 5bbc98e9..71f0a413 100644 --- a/symphony/bdk/core/service/connection/connection_service.py +++ b/symphony/bdk/core/service/connection/connection_service.py @@ -19,7 +19,9 @@ class OboConnectionService: * Remove a connection with a user """ - def __init__(self, connection_api: ConnectionApi, auth_session: AuthSession, retry_config: BdkRetryConfig): + def __init__( + self, connection_api: ConnectionApi, auth_session: AuthSession, retry_config: BdkRetryConfig + ): self._connection_api = connection_api self._auth_session = auth_session self._retry_config = retry_config @@ -35,17 +37,12 @@ async def get_connection(self, user_id: int) -> UserConnection: :return: Connection status with the specified user. """ - params = { - 'user_id': str(user_id), - 'session_token': await self._auth_session.session_token - } + params = {"user_id": str(user_id), "session_token": await self._auth_session.session_token} return await self._connection_api.v1_connection_user_user_id_info_get(**params) @retry async def list_connections( - self, - status: ConnectionStatus = ConnectionStatus.ALL, - user_ids: [int] = None + self, status: ConnectionStatus = ConnectionStatus.ALL, user_ids: [int] = None ) -> [UserConnection]: """ List all connection statuses of the requesting user with external or specified users. @@ -62,12 +59,9 @@ async def list_connections( :return: List of connection statuses with the specified users and status. """ - params = { - 'status': status.value, - 'session_token': await self._auth_session.session_token - } + params = {"status": status.value, "session_token": await self._auth_session.session_token} if user_ids is not None: - params['user_ids'] = ','.join(map(str, user_ids)) + params["user_ids"] = ",".join(map(str, user_ids)) user_connection_list = await self._connection_api.v1_connection_list_get(**params) return user_connection_list.value @@ -85,8 +79,8 @@ async def create_connection(self, user_id: int) -> UserConnection: """ user_connection_request = UserConnectionRequest(user_id=user_id) params = { - 'connection_request': user_connection_request, - 'session_token': await self._auth_session.session_token + "connection_request": user_connection_request, + "session_token": await self._auth_session.session_token, } return await self._connection_api.v1_connection_create_post(**params) @@ -103,8 +97,8 @@ async def accept_connection(self, user_id: int) -> UserConnection: """ user_connection_request = UserConnectionRequest(user_id=user_id) params = { - 'connection_request': user_connection_request, - 'session_token': await self._auth_session.session_token + "connection_request": user_connection_request, + "session_token": await self._auth_session.session_token, } return await self._connection_api.v1_connection_accept_post(**params) @@ -121,8 +115,8 @@ async def reject_connection(self, user_id: int) -> UserConnection: """ user_connection_request = UserConnectionRequest(user_id=user_id) params = { - 'connection_request': user_connection_request, - 'session_token': await self._auth_session.session_token + "connection_request": user_connection_request, + "session_token": await self._auth_session.session_token, } return await self._connection_api.v1_connection_reject_post(**params) @@ -135,10 +129,7 @@ async def remove_connection(self, user_id: int) -> None: :param user_id: The id of the user with whom we want to remove the connection. """ - params = { - 'uid': user_id, - 'session_token': await self._auth_session.session_token - } + params = {"uid": user_id, "session_token": await self._auth_session.session_token} await self._connection_api.v1_connection_user_uid_remove_post(**params) diff --git a/symphony/bdk/core/service/connection/model/connection_status.py b/symphony/bdk/core/service/connection/model/connection_status.py index 921f57e7..2e9f6f3b 100644 --- a/symphony/bdk/core/service/connection/model/connection_status.py +++ b/symphony/bdk/core/service/connection/model/connection_status.py @@ -5,6 +5,7 @@ class ConnectionStatus(enum.Enum): """The list of all possible values for the request listing connection status. See: `List Connections `_ """ + ALL = "ALL" PENDING_INCOMING = "PENDING_INCOMING" PENDING_OUTGOING = "PENDING_OUTGOING" diff --git a/symphony/bdk/core/service/datafeed/abstract_ackId_event_loop.py b/symphony/bdk/core/service/datafeed/abstract_ackId_event_loop.py index 8f72350b..449f6b99 100644 --- a/symphony/bdk/core/service/datafeed/abstract_ackId_event_loop.py +++ b/symphony/bdk/core/service/datafeed/abstract_ackId_event_loop.py @@ -1,6 +1,6 @@ import logging import time -from abc import abstractmethod, ABC +from abc import ABC, abstractmethod from symphony.bdk.core.auth.auth_session import AuthSession from symphony.bdk.core.config.model.bdk_config import BdkConfig @@ -15,11 +15,15 @@ class AbstractAckIdEventLoop(AbstractDatafeedLoop, ABC): - """Base class for implementing datafeed types that relies on an ackId. - """ + """Base class for implementing datafeed types that relies on an ackId.""" - def __init__(self, datafeed_api: DatafeedApi, session_service: SessionService, auth_session: AuthSession, - config: BdkConfig): + def __init__( + self, + datafeed_api: DatafeedApi, + session_service: SessionService, + auth_session: AuthSession, + config: BdkConfig, + ): """ :param datafeed_api: DatafeedApi to request the service @@ -45,10 +49,12 @@ async def _run_all_listener_tasks(self, events): elapsed = time.time() - start if elapsed > EVENT_PROCESSING_MAX_DURATION_SECONDS: - logging.warning("Events processing took longer than %s seconds, " - "this might lead to events being re-queued in datafeed and re-dispatched. " - "You might want to consider processing the event in a separated asyncio task if needed.", - EVENT_PROCESSING_MAX_DURATION_SECONDS) + logging.warning( + "Events processing took longer than %s seconds, " + "this might lead to events being re-queued in datafeed and re-dispatched. " + "You might want to consider processing the event in a separated asyncio task if needed.", + EVENT_PROCESSING_MAX_DURATION_SECONDS, + ) return await self._are_tasks_successful(done_tasks) @@ -58,13 +64,17 @@ async def _are_tasks_successful(self, tasks): exception = task.exception() if exception: if isinstance(exception, EventError): - logger.warning("Failed to process events inside %s, " - "will not update ack id, events will be re-queued", - task.get_name(), - exc_info=exception) + logger.warning( + "Failed to process events inside %s, " + "will not update ack id, events will be re-queued", + task.get_name(), + exc_info=exception, + ) success = False else: - logging.debug("Exception occurred inside %s", task.get_name(), exc_info=exception) + logging.debug( + "Exception occurred inside %s", task.get_name(), exc_info=exception + ) return success @abstractmethod diff --git a/symphony/bdk/core/service/datafeed/abstract_datafeed_loop.py b/symphony/bdk/core/service/datafeed/abstract_datafeed_loop.py index 19913baa..6fc9d1eb 100644 --- a/symphony/bdk/core/service/datafeed/abstract_datafeed_loop.py +++ b/symphony/bdk/core/service/datafeed/abstract_datafeed_loop.py @@ -1,5 +1,5 @@ -"""This module gathers all base classes related to the datafeed loop and real time events. -""" +"""This module gathers all base classes related to the datafeed loop and real time events.""" + import asyncio import logging from abc import ABC, abstractmethod @@ -21,8 +21,8 @@ class DatafeedVersion(Enum): - """Enum of all possible datafeed versions. - """ + """Enum of all possible datafeed versions.""" + V1 = "v1" V2 = "v2" @@ -36,6 +36,7 @@ class RealTimeEvent(Enum): First element in enum value corresponds to the listener method who should be called when given event is received. Second element in enum value corresponds to the field name of event in the received payload. """ + MESSAGESENT = ("on_message_sent", "message_sent") SHAREDPOST = ("on_shared_post", "shared_post") INSTANTMESSAGECREATED = ("on_instant_message_created", "instant_message_created") @@ -46,7 +47,10 @@ class RealTimeEvent(Enum): USERREQUESTEDTOJOINROOM = ("on_user_requested_to_join_room", "user_requested_to_join_room") USERJOINEDROOM = ("on_user_joined_room", "user_joined_room") USERLEFTROOM = ("on_user_left_room", "user_left_room") - ROOMMEMBERPROMOTEDTOOWNER = ("on_room_member_promoted_to_owner", "room_member_promoted_to_owner") + ROOMMEMBERPROMOTEDTOOWNER = ( + "on_room_member_promoted_to_owner", + "room_member_promoted_to_owner", + ) ROOMMEMBERDEMOTEDFROMOWNER = ("on_room_demoted_from_owner", "room_member_demoted_from_owner") CONNECTIONREQUESTED = ("on_connection_requested", "connection_requested") CONNECTIONACCEPTED = ("on_connection_accepted", "connection_accepted") @@ -66,8 +70,13 @@ class AbstractDatafeedLoop(ABC): event by the subscribed listeners. """ - def __init__(self, datafeed_api: DatafeedApi, session_service: SessionService, auth_session: AuthSession, - config: BdkConfig): + def __init__( + self, + datafeed_api: DatafeedApi, + session_service: SessionService, + auth_session: AuthSession, + config: BdkConfig, + ): """ :param datafeed_api: DatafeedApi to request the service diff --git a/symphony/bdk/core/service/datafeed/abstract_datahose_loop.py b/symphony/bdk/core/service/datafeed/abstract_datahose_loop.py index 3252b630..b14183f1 100644 --- a/symphony/bdk/core/service/datafeed/abstract_datahose_loop.py +++ b/symphony/bdk/core/service/datafeed/abstract_datahose_loop.py @@ -9,8 +9,8 @@ class AbstractDatahoseLoop(ABC): """Base class for implementing the datahose services. - A datahose service can help a bot to get all the received real-time events that are set - as filters in the configuration. + A datahose service can help a bot to get all the received real-time events that are set + as filters in the configuration. """ @abstractmethod diff --git a/symphony/bdk/core/service/datafeed/datafeed_loop_v1.py b/symphony/bdk/core/service/datafeed/datafeed_loop_v1.py index 5e716c68..129c9a0a 100644 --- a/symphony/bdk/core/service/datafeed/datafeed_loop_v1.py +++ b/symphony/bdk/core/service/datafeed/datafeed_loop_v1.py @@ -4,7 +4,9 @@ from symphony.bdk.core.retry.strategy import read_datafeed_retry from symphony.bdk.core.service.datafeed.abstract_datafeed_loop import AbstractDatafeedLoop from symphony.bdk.core.service.datafeed.exception import EventError -from symphony.bdk.core.service.datafeed.on_disk_datafeed_id_repository import OnDiskDatafeedIdRepository +from symphony.bdk.core.service.datafeed.on_disk_datafeed_id_repository import ( + OnDiskDatafeedIdRepository, +) logger = logging.getLogger(__name__) @@ -32,7 +34,9 @@ class DatafeedLoopV1(AbstractDatafeedLoop): def __init__(self, datafeed_api, session_service, auth_session, config, repository=None): super().__init__(datafeed_api, session_service, auth_session, config) - self._datafeed_repository = OnDiskDatafeedIdRepository(config) if repository is None else repository + self._datafeed_repository = ( + OnDiskDatafeedIdRepository(config) if repository is None else repository + ) self._datafeed_id = None async def start(self): @@ -57,8 +61,9 @@ async def _prepare_datafeed(self): async def recreate_datafeed(self): session_token = await self._auth_session.session_token key_manager_token = await self._auth_session.key_manager_token - response = await self._datafeed_api.v4_datafeed_create_post(session_token=session_token, - key_manager_token=key_manager_token) + response = await self._datafeed_api.v4_datafeed_create_post( + session_token=session_token, key_manager_token=key_manager_token + ) datafeed_id = response.id self._datafeed_repository.write(datafeed_id) self._datafeed_id = datafeed_id @@ -73,17 +78,20 @@ async def _run_loop_iteration(self): async def _read_datafeed(self): session_token = await self._auth_session.session_token key_manager_token = await self._auth_session.key_manager_token - events = await self._datafeed_api.v4_datafeed_id_read_get(id=self._datafeed_id, - session_token=session_token, - key_manager_token=key_manager_token) + events = await self._datafeed_api.v4_datafeed_id_read_get( + id=self._datafeed_id, session_token=session_token, key_manager_token=key_manager_token + ) return events.value if events is not None and events.value else [] async def _log_listener_exception(self, task): exception = task.exception() if exception: if isinstance(exception, EventError): - logger.warning("EventError occurred inside %s. " - "EventError is not supported for DFv1, events will not get re-queued", - task.get_name(), exc_info=exception) + logger.warning( + "EventError occurred inside %s. " + "EventError is not supported for DFv1, events will not get re-queued", + task.get_name(), + exc_info=exception, + ) else: logging.debug("Exception occurred inside %s", task.get_name(), exc_info=exception) diff --git a/symphony/bdk/core/service/datafeed/datafeed_loop_v2.py b/symphony/bdk/core/service/datafeed/datafeed_loop_v2.py index 63c6ef01..ae7d8775 100644 --- a/symphony/bdk/core/service/datafeed/datafeed_loop_v2.py +++ b/symphony/bdk/core/service/datafeed/datafeed_loop_v2.py @@ -23,24 +23,29 @@ class DatafeedLoopV2(AbstractAckIdEventLoop): """A class for implementing the datafeed v2 loop service. - This service will be started by calling :func:`~DatafeedLoopV2.start`. + This service will be started by calling :func:`~DatafeedLoopV2.start`. - On the very first run, the BDK bot will try to retrieve the list of datafeed to which it is listening. - Since each bot should only listen to just one datafeed, the first datafeed in the list will be used by - the bot to be listened to. If the retrieved list is empty, the BDK bot will create a new datafeed to listen. + On the very first run, the BDK bot will try to retrieve the list of datafeed to which it is listening. + Since each bot should only listen to just one datafeed, the first datafeed in the list will be used by + the bot to be listened to. If the retrieved list is empty, the BDK bot will create a new datafeed to listen. - The BDK bot will listen to this datafeed to get all the received real-time events. + The BDK bot will listen to this datafeed to get all the received real-time events. - If this datafeed becomes stale or faulty, the BDK bot will create the new one for listening. + If this datafeed becomes stale or faulty, the BDK bot will create the new one for listening. - This service will be stopped by calling :func:`~DatafeedLoopV1.stop` + This service will be stopped by calling :func:`~DatafeedLoopV1.stop` - If the datafeed service is stopped during a read datafeed call, it has to wait until the last read finish to be - really stopped - """ + If the datafeed service is stopped during a read datafeed call, it has to wait until the last read finish to be + really stopped + """ - def __init__(self, datafeed_api: DatafeedApi, session_service: SessionService, auth_session: AuthSession, - config: BdkConfig): + def __init__( + self, + datafeed_api: DatafeedApi, + session_service: SessionService, + auth_session: AuthSession, + config: BdkConfig, + ): super().__init__(datafeed_api, session_service, auth_session, config) self._datafeed_id = None @@ -69,7 +74,7 @@ async def _read_events(self): "session_token": await self._auth_session.session_token, "key_manager_token": await self._auth_session.key_manager_token, "datafeed_id": self._datafeed_id, - "ack_id": AckId(ack_id=self._ack_id) + "ack_id": AckId(ack_id=self._ack_id), } return await self._datafeed_api.read_datafeed(**params) @@ -84,8 +89,9 @@ async def recreate_datafeed(self): async def _retrieve_datafeed(self) -> Optional[V5Datafeed]: session_token = await self._auth_session.session_token key_manager_token = await self._auth_session.key_manager_token - datafeeds = await self._datafeed_api.list_datafeed(session_token=session_token, - key_manager_token=key_manager_token) + datafeeds = await self._datafeed_api.list_datafeed( + session_token=session_token, key_manager_token=key_manager_token + ) datafeeds = list(filter(lambda df: DATAFEED_V2_ID_PATTERN.match(df.id), datafeeds)) if datafeeds: @@ -97,14 +103,18 @@ async def _create_datafeed(self) -> V5Datafeed: session_token = await self._auth_session.session_token key_manager_token = await self._auth_session.key_manager_token - return await self._datafeed_api.create_datafeed(session_token=session_token, - key_manager_token=key_manager_token, - body=V5DatafeedCreateBody()) + return await self._datafeed_api.create_datafeed( + session_token=session_token, + key_manager_token=key_manager_token, + body=V5DatafeedCreateBody(), + ) @retry async def _delete_datafeed(self) -> None: session_token = await self._auth_session.session_token key_manager_token = await self._auth_session.key_manager_token - await self._datafeed_api.delete_datafeed(datafeed_id=self._datafeed_id, - session_token=session_token, - key_manager_token=key_manager_token) + await self._datafeed_api.delete_datafeed( + datafeed_id=self._datafeed_id, + session_token=session_token, + key_manager_token=key_manager_token, + ) diff --git a/symphony/bdk/core/service/datafeed/datahose_loop.py b/symphony/bdk/core/service/datafeed/datahose_loop.py index ede45472..37501a6a 100644 --- a/symphony/bdk/core/service/datafeed/datahose_loop.py +++ b/symphony/bdk/core/service/datafeed/datahose_loop.py @@ -1,11 +1,11 @@ import logging +from symphony.bdk.core.auth.auth_session import AuthSession +from symphony.bdk.core.config.model.bdk_config import BdkConfig from symphony.bdk.core.retry import retry from symphony.bdk.core.retry.strategy import read_datahose_retry from symphony.bdk.core.service.datafeed.abstract_ackId_event_loop import AbstractAckIdEventLoop from symphony.bdk.core.service.datafeed.abstract_datahose_loop import AbstractDatahoseLoop -from symphony.bdk.core.auth.auth_session import AuthSession -from symphony.bdk.core.config.model.bdk_config import BdkConfig from symphony.bdk.core.service.session.session_service import SessionService from symphony.bdk.gen.agent_api.datafeed_api import DatafeedApi from symphony.bdk.gen.agent_model.v5_events_read_body import V5EventsReadBody @@ -20,21 +20,30 @@ class DatahoseLoop(AbstractAckIdEventLoop, AbstractDatahoseLoop): """A class for implementing the datahose loop service. - This service will be started by calling :func:`~DatahoseLoop.start`. + This service will be started by calling :func:`~DatahoseLoop.start`. - The BDK bot will listen to this datahose to get all the received real-time events that are set - as event_types in the configuration. + The BDK bot will listen to this datahose to get all the received real-time events that are set + as event_types in the configuration. - This service will be stopped by calling :func:`~DatahoseLoop.stop` + This service will be stopped by calling :func:`~DatahoseLoop.stop` """ - def __init__(self, datafeed_api: DatafeedApi, session_service: SessionService, auth_session: AuthSession, - config: BdkConfig): + def __init__( + self, + datafeed_api: DatafeedApi, + session_service: SessionService, + auth_session: AuthSession, + config: BdkConfig, + ): super().__init__(datafeed_api, session_service, auth_session, config) if config.datahose is not None: - not_truncated_tag = config.datahose.tag if config.datahose.tag is not None \ - else TYPE + "-" + config.bot.username if config.bot.username is not None \ + not_truncated_tag = ( + config.datahose.tag + if config.datahose.tag is not None + else TYPE + "-" + config.bot.username + if config.bot.username is not None else TYPE + ) self._tag = not_truncated_tag[:DATAHOSE_TAG_MAX_LENGTH] self._retry = config.datahose.retry self._event_types = config.datahose.event_types @@ -56,7 +65,9 @@ async def _read_events(self): params = { "session_token": await self._auth_session.session_token, "key_manager_token": await self._auth_session.key_manager_token, - "body": V5EventsReadBody(type=TYPE, tag=self._tag, event_types=self._event_types, ack_id=self._ack_id) + "body": V5EventsReadBody( + type=TYPE, tag=self._tag, event_types=self._event_types, ack_id=self._ack_id + ), } return await self._datafeed_api.read_events(**params) diff --git a/symphony/bdk/core/service/datafeed/on_disk_datafeed_id_repository.py b/symphony/bdk/core/service/datafeed/on_disk_datafeed_id_repository.py index b09757fb..4b6fadaa 100644 --- a/symphony/bdk/core/service/datafeed/on_disk_datafeed_id_repository.py +++ b/symphony/bdk/core/service/datafeed/on_disk_datafeed_id_repository.py @@ -2,6 +2,7 @@ import os from abc import ABC, abstractmethod from pathlib import Path + from symphony.bdk.core.config.model.bdk_config import BdkConfig logger = logging.getLogger(__name__) @@ -30,6 +31,7 @@ def read(self): class OnDiskDatafeedIdRepository(DatafeedIdRepository): """Implementation of DatafeedIdRepository interface for persisting a datafeed id on disk.""" + DATAFEED_ID_FILE = "datafeed.id" def __init__(self, config: BdkConfig): @@ -42,8 +44,10 @@ def write(self, datafeed_id, agent_base_path=""): def read(self) -> str: logger.debug("Retrieving datafeed id from file %s", self.datafeed_id_file_path.absolute()) if not os.path.exists(self.datafeed_id_file_path): - logger.debug("Could not retrieve datafeed id from file %s: file not found", - self.datafeed_id_file_path.absolute()) + logger.debug( + "Could not retrieve datafeed id from file %s: file not found", + self.datafeed_id_file_path.absolute(), + ) return None return self._read_in_file() @@ -56,8 +60,10 @@ def _read_in_file(self): def _read_in_line(self, line): index = line.find("@") if index == -1: - logger.debug("Could not retrieve datafeed id from file %s: file without datafeed id information", - self.datafeed_id_file_path.absolute()) + logger.debug( + "Could not retrieve datafeed id from file %s: file without datafeed id information", + self.datafeed_id_file_path.absolute(), + ) return None datafeed_id = line[0:index] logger.debug("Retrieved datafeed id: %s", datafeed_id) diff --git a/symphony/bdk/core/service/datafeed/real_time_event_listener.py b/symphony/bdk/core/service/datafeed/real_time_event_listener.py index 7a902eb0..a982ca9a 100644 --- a/symphony/bdk/core/service/datafeed/real_time_event_listener.py +++ b/symphony/bdk/core/service/datafeed/real_time_event_listener.py @@ -1,21 +1,25 @@ +from symphony.bdk.gen.agent_model.v4_connection_accepted import V4ConnectionAccepted +from symphony.bdk.gen.agent_model.v4_connection_requested import V4ConnectionRequested from symphony.bdk.gen.agent_model.v4_event import V4Event -from symphony.bdk.gen.agent_model.v4_message_sent import V4MessageSent from symphony.bdk.gen.agent_model.v4_initiator import V4Initiator -from symphony.bdk.gen.agent_model.v4_shared_post import V4SharedPost from symphony.bdk.gen.agent_model.v4_instant_message_created import V4InstantMessageCreated +from symphony.bdk.gen.agent_model.v4_message_sent import V4MessageSent +from symphony.bdk.gen.agent_model.v4_message_suppressed import V4MessageSuppressed from symphony.bdk.gen.agent_model.v4_room_created import V4RoomCreated -from symphony.bdk.gen.agent_model.v4_room_updated import V4RoomUpdated from symphony.bdk.gen.agent_model.v4_room_deactivated import V4RoomDeactivated +from symphony.bdk.gen.agent_model.v4_room_member_demoted_from_owner import ( + V4RoomMemberDemotedFromOwner, +) +from symphony.bdk.gen.agent_model.v4_room_member_promoted_to_owner import ( + V4RoomMemberPromotedToOwner, +) from symphony.bdk.gen.agent_model.v4_room_reactivated import V4RoomReactivated -from symphony.bdk.gen.agent_model.v4_user_requested_to_join_room import V4UserRequestedToJoinRoom +from symphony.bdk.gen.agent_model.v4_room_updated import V4RoomUpdated +from symphony.bdk.gen.agent_model.v4_shared_post import V4SharedPost +from symphony.bdk.gen.agent_model.v4_symphony_elements_action import V4SymphonyElementsAction from symphony.bdk.gen.agent_model.v4_user_joined_room import V4UserJoinedRoom from symphony.bdk.gen.agent_model.v4_user_left_room import V4UserLeftRoom -from symphony.bdk.gen.agent_model.v4_room_member_promoted_to_owner import V4RoomMemberPromotedToOwner -from symphony.bdk.gen.agent_model.v4_room_member_demoted_from_owner import V4RoomMemberDemotedFromOwner -from symphony.bdk.gen.agent_model.v4_connection_requested import V4ConnectionRequested -from symphony.bdk.gen.agent_model.v4_connection_accepted import V4ConnectionAccepted -from symphony.bdk.gen.agent_model.v4_message_suppressed import V4MessageSuppressed -from symphony.bdk.gen.agent_model.v4_symphony_elements_action import V4SymphonyElementsAction +from symphony.bdk.gen.agent_model.v4_user_requested_to_join_room import V4UserRequestedToJoinRoom from symphony.bdk.gen.pod_model.user_v2 import UserV2 @@ -42,21 +46,23 @@ async def is_accepting_event(event: V4Event, bot_info: UserV2) -> bool: return False async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): - """ Called when a MESSAGESENT event is received. + """Called when a MESSAGESENT event is received. :param initiator: Event initiator. :param event: Message sent payload. """ async def on_shared_post(self, initiator: V4Initiator, event: V4SharedPost): - """ Called when an INSTANTMESSAGECREATED event is received. + """Called when an INSTANTMESSAGECREATED event is received. :param initiator: Event initiator. :param event: Shared post payload. """ - async def on_instant_message_created(self, initiator: V4Initiator, event: V4InstantMessageCreated): - """ Called when a ROOMCREATED event is received. + async def on_instant_message_created( + self, initiator: V4Initiator, event: V4InstantMessageCreated + ): + """Called when a ROOMCREATED event is received. :param initiator: Event initiator. :param event: Instant Message Created payload. @@ -90,7 +96,9 @@ async def on_room_reactivated(self, initiator: V4Initiator, event: V4RoomReactiv :param event: Room Reactivated payload. """ - async def on_user_requested_to_join_room(self, initiator: V4Initiator, event: V4UserRequestedToJoinRoom): + async def on_user_requested_to_join_room( + self, initiator: V4Initiator, event: V4UserRequestedToJoinRoom + ): """ :param initiator: Event initiator. @@ -111,14 +119,18 @@ async def on_user_left_room(self, initiator: V4Initiator, event: V4UserLeftRoom) :param event: User Left Room payload. """ - async def on_room_member_promoted_to_owner(self, initiator: V4Initiator, event: V4RoomMemberPromotedToOwner): + async def on_room_member_promoted_to_owner( + self, initiator: V4Initiator, event: V4RoomMemberPromotedToOwner + ): """ :param initiator: Event initiator. :param event: Room Member Promoted To Owner payload. """ - async def on_room_demoted_from_owner(self, initiator: V4Initiator, event: V4RoomMemberDemotedFromOwner): + async def on_room_demoted_from_owner( + self, initiator: V4Initiator, event: V4RoomMemberDemotedFromOwner + ): """ :param initiator: Event initiator. @@ -146,7 +158,9 @@ async def on_message_suppressed(self, initiator: V4Initiator, event: V4MessageSu :param event: Message Suppressed payload. """ - async def on_symphony_elements_action(self, initiator: V4Initiator, event: V4SymphonyElementsAction): + async def on_symphony_elements_action( + self, initiator: V4Initiator, event: V4SymphonyElementsAction + ): """ :param initiator: Event initiator. diff --git a/symphony/bdk/core/service/exception.py b/symphony/bdk/core/service/exception.py index df650e33..6942498e 100644 --- a/symphony/bdk/core/service/exception.py +++ b/symphony/bdk/core/service/exception.py @@ -1,12 +1,9 @@ -"""Module containing all service related exception. -""" +"""Module containing all service related exception.""" class MessageParserError(Exception): - """Raised when the message is not found in the correct format - """ + """Raised when the message is not found in the correct format""" class MessageCreationError(Exception): - """Raised when a :py:class:`~symphony.bdk.core.service.message.model.Message` failed to be created - """ + """Raised when a :py:class:`~symphony.bdk.core.service.message.model.Message` failed to be created""" diff --git a/symphony/bdk/core/service/health/health_service.py b/symphony/bdk/core/service/health/health_service.py index 12c62d82..c34afafa 100644 --- a/symphony/bdk/core/service/health/health_service.py +++ b/symphony/bdk/core/service/health/health_service.py @@ -1,21 +1,22 @@ from symphony.bdk.core.config.model.bdk_retry_config import BdkRetryConfig -from symphony.bdk.gen.agent_api.system_api import SystemApi from symphony.bdk.gen.agent_api.signals_api import SignalsApi +from symphony.bdk.gen.agent_api.system_api import SystemApi from symphony.bdk.gen.agent_model.agent_info import AgentInfo - from symphony.bdk.gen.agent_model.v3_health import V3Health class HealthService: """Service class for checking health of the Agent server.""" - def __init__(self, system_api: SystemApi, signals_api: SignalsApi, retry_config: BdkRetryConfig): + def __init__( + self, system_api: SystemApi, signals_api: SignalsApi, retry_config: BdkRetryConfig + ): self._system_api = system_api self._signals_api = signals_api self._retry_config = retry_config async def health_check(self) -> V3Health: - """ Returns the connectivity status of your Agent server. + """Returns the connectivity status of your Agent server. Wraps the `Health Check v3 `_ endpoint. If your Agent server is started and running properly, the status value will be UP. Available on Agent 2.57.0 and above. diff --git a/symphony/bdk/core/service/message/message_parser.py b/symphony/bdk/core/service/message/message_parser.py index c317e1a8..c51e0f75 100644 --- a/symphony/bdk/core/service/message/message_parser.py +++ b/symphony/bdk/core/service/message/message_parser.py @@ -2,14 +2,14 @@ extracting entities like Mentions, Hashtags, Cashtags, Emojis and extracting text content from presentationML contained in the message. """ -import json -import html +import html +import json from enum import Enum from json import JSONDecodeError from typing import Dict -from defusedxml.ElementTree import fromstring, tostring, ParseError +from defusedxml.ElementTree import ParseError, fromstring, tostring from symphony.bdk.core.service.exception import MessageParserError from symphony.bdk.gen.agent_model.v4_message import V4Message @@ -25,11 +25,13 @@ def get_text_content_from_message(message: V4Message) -> str: escaped_text_content = tostring(fromstring(message.message), method="text").decode().strip() return html.unescape(escaped_text_content) except ParseError as exc: - raise MessageParserError("Unable to parse the PresentationML, it is not in the correct format.") from exc + raise MessageParserError( + "Unable to parse the PresentationML, it is not in the correct format." + ) from exc def get_mentions(message: V4Message) -> [int]: - """ Parse data inside an incoming message and returns a list containing the user ids corresponding + """Parse data inside an incoming message and returns a list containing the user ids corresponding to the users mentioned :param message: incoming V4 message to be parsed @@ -40,7 +42,7 @@ def get_mentions(message: V4Message) -> [int]: def get_hashtags(message: V4Message) -> [str]: - """ Parse data inside an incoming message and returns a list containing the text of the hashtags found + """Parse data inside an incoming message and returns a list containing the text of the hashtags found :param message: message incoming V4 message to be parsed :return: list of hashtags contained in the message @@ -49,7 +51,7 @@ def get_hashtags(message: V4Message) -> [str]: def get_cashtags(message: V4Message) -> [str]: - """ Parse data inside an incoming message and returns a list containing the text of the cashtags found + """Parse data inside an incoming message and returns a list containing the text of the cashtags found :param message: message incoming V4 message to be parsed :return: list of cashtags contained in the message @@ -58,7 +60,7 @@ def get_cashtags(message: V4Message) -> [str]: def get_emojis(message: V4Message) -> Dict[str, str]: - """ Parse data inside an incoming message and returns a map containing the list of emojis found. + """Parse data inside an incoming message and returns a map containing the list of emojis found. Key of the map are the annotation used to identify the emoji and the values are the their unicode. :param message: message incoming V4 message to be parsed diff --git a/symphony/bdk/core/service/message/message_service.py b/symphony/bdk/core/service/message/message_service.py index e1f9427a..9b8eb447 100644 --- a/symphony/bdk/core/service/message/message_service.py +++ b/symphony/bdk/core/service/message/message_service.py @@ -1,16 +1,21 @@ -from typing import Union, List, Tuple, IO, AsyncGenerator +from typing import IO, AsyncGenerator, List, Tuple, Union from symphony.bdk.core.auth.auth_session import AuthSession from symphony.bdk.core.config.model.bdk_retry_config import BdkRetryConfig +from symphony.bdk.core.retry import retry from symphony.bdk.core.service.message.model import Message -from symphony.bdk.core.service.message.multi_attachments_messages_api import MultiAttachmentsMessagesApi +from symphony.bdk.core.service.message.multi_attachments_messages_api import ( + MultiAttachmentsMessagesApi, +) from symphony.bdk.core.service.pagination import offset_based_pagination from symphony.bdk.gen.agent_api.attachments_api import AttachmentsApi from symphony.bdk.gen.agent_model.message_search_query import MessageSearchQuery from symphony.bdk.gen.agent_model.v4_import_response import V4ImportResponse from symphony.bdk.gen.agent_model.v4_imported_message import V4ImportedMessage from symphony.bdk.gen.agent_model.v4_message import V4Message -from symphony.bdk.gen.agent_model.v4_message_blast_response import V4MessageBlastResponse +from symphony.bdk.gen.agent_model.v4_message_blast_response import ( + V4MessageBlastResponse, +) from symphony.bdk.gen.agent_model.v4_message_import_list import V4MessageImportList from symphony.bdk.gen.pod_api.default_api import DefaultApi from symphony.bdk.gen.pod_api.message_api import MessageApi @@ -18,19 +23,27 @@ from symphony.bdk.gen.pod_api.pod_api import PodApi from symphony.bdk.gen.pod_api.streams_api import StreamsApi from symphony.bdk.gen.pod_model.message_metadata_response import MessageMetadataResponse -from symphony.bdk.gen.pod_model.message_receipt_detail_response import MessageReceiptDetailResponse +from symphony.bdk.gen.pod_model.message_receipt_detail_response import ( + MessageReceiptDetailResponse, +) from symphony.bdk.gen.pod_model.message_status import MessageStatus -from symphony.bdk.gen.pod_model.message_suppression_response import MessageSuppressionResponse +from symphony.bdk.gen.pod_model.message_suppression_response import ( + MessageSuppressionResponse, +) from symphony.bdk.gen.pod_model.stream_attachment_item import StreamAttachmentItem -from symphony.bdk.core.retry import retry - class OboMessageService: """Class exposing OBO enabled endpoints for message management, e.g. send a message.""" - def __init__(self, messages_api: MultiAttachmentsMessagesApi, message_suppression_api: MessageSuppressionApi, - pod_api: PodApi, auth_session: AuthSession, retry_config: BdkRetryConfig): + def __init__( + self, + messages_api: MultiAttachmentsMessagesApi, + message_suppression_api: MessageSuppressionApi, + pod_api: PodApi, + auth_session: AuthSession, + retry_config: BdkRetryConfig, + ): self._messages_api = messages_api self._message_suppression_api = message_suppression_api self._pod_api = pod_api @@ -38,12 +51,12 @@ def __init__(self, messages_api: MultiAttachmentsMessagesApi, message_suppressio self._retry_config = retry_config async def send_message( - self, - stream_id: str, - message: Union[str, Message], - data=None, - version: str = "", - attachment: List[Union[IO, Tuple[IO, IO]]] = None + self, + stream_id: str, + message: Union[str, Message], + data=None, + version: str = "", + attachment: List[Union[IO, Tuple[IO, IO]]] = None, ) -> V4Message: """Send a message to an existing stream. See: `Create Message `_ @@ -62,20 +75,29 @@ async def send_message( :return: a V4Message object containing the details of the sent message. """ - message_object = message if isinstance(message, Message) else Message(content=message, data=data, - version=version, attachments=attachment) - return await self._send_message(stream_id, message_object.content, message_object.data, message_object.version, - message_object.attachments, message_object.previews) + message_object = ( + message + if isinstance(message, Message) + else Message(content=message, data=data, version=version, attachments=attachment) + ) + return await self._send_message( + stream_id, + message_object.content, + message_object.data, + message_object.version, + message_object.attachments, + message_object.previews, + ) @retry async def _send_message( - self, - stream_id: str, - message: str, - data: str = "", - version: str = "", - attachment: List[IO] = None, - preview: List[IO] = None + self, + stream_id: str, + message: str, + data: str = "", + version: str = "", + attachment: List[IO] = None, + preview: List[IO] = None, ) -> V4Message: """Send a message to an existing stream. See: `Create Message `_ @@ -98,7 +120,7 @@ async def _send_message( "key_manager_token": await self._auth_session.key_manager_token, "message": message, "data": data, - "version": version + "version": version, } if attachment is not None: params["attachment"] = attachment if isinstance(attachment, list) else [attachment] @@ -108,10 +130,7 @@ async def _send_message( return await self._messages_api.v4_stream_sid_multi_attachment_message_create_post(**params) @retry - async def suppress_message( - self, - message_id: str - ) -> MessageSuppressionResponse: + async def suppress_message(self, message_id: str) -> MessageSuppressionResponse: """Suppresses a message, preventing its contents from being displayed to users. See: `Suppress Message `_ @@ -122,13 +141,22 @@ async def suppress_message( """ params = { "id": message_id, - "session_token": await self._auth_session.session_token + "session_token": await self._auth_session.session_token, } - return await self._message_suppression_api.v1_admin_messagesuppression_id_suppress_post(**params) + return await self._message_suppression_api.v1_admin_messagesuppression_id_suppress_post( + **params + ) @retry - async def update_message(self, stream_id: str, message_id: str, message: Union[str, Message], data=None, - version: str = "", silent=True) -> V4Message: + async def update_message( + self, + stream_id: str, + message_id: str, + message: Union[str, Message], + data=None, + version: str = "", + silent=True, + ) -> V4Message: """Update an existing message. The existing message must be a valid social message, that has not been deleted. See: `Update Message `_ @@ -145,8 +173,11 @@ async def update_message(self, stream_id: str, message_id: str, message: Union[s :return: a V4Message object containing the details of the updated message. """ - message_object = message if isinstance(message, Message) else Message(content=message, data=data, silent=silent, - version=version) + message_object = ( + message + if isinstance(message, Message) + else Message(content=message, data=data, silent=silent, version=version) + ) params = { "sid": stream_id, @@ -156,7 +187,7 @@ async def update_message(self, stream_id: str, message_id: str, message: Union[s "message": message_object.content, "data": message_object.data, "version": message_object.version, - "silent": str(message_object.silent) + "silent": str(message_object.silent), } return await self._messages_api.v4_stream_sid_message_mid_update_post(**params) @@ -168,19 +199,17 @@ async def get_attachment_types(self) -> List[str]: :return: a list of String containing all allowed file extensions for attachments. """ - params = { - "session_token": await self._auth_session.session_token - } + params = {"session_token": await self._auth_session.session_token} type_list = await self._pod_api.v1_files_allowed_types_get(**params) return type_list.value - + async def blast_message( - self, - stream_ids: List[str], - message: Union[str, Message], - data=None, - version: str = "", - attachment: List[Union[IO, Tuple[IO, IO]]] = None + self, + stream_ids: List[str], + message: Union[str, Message], + data=None, + version: str = "", + attachment: List[Union[IO, Tuple[IO, IO]]] = None, ) -> V4MessageBlastResponse: """Send a message to multiple existing streams. See: `Blast Message `_ @@ -198,20 +227,29 @@ async def blast_message( The limit is set to 30Mb total size; also, it is recommended not to exceed 25 files. :return: """ - message_object = message if isinstance(message, Message) else Message(content=message, data=data, - version=version, attachments=attachment) - return await self._blast_message(stream_ids, message_object.content, message_object.data, - message_object.version, message_object.attachments, message_object.previews) - + message_object = ( + message + if isinstance(message, Message) + else Message(content=message, data=data, version=version, attachments=attachment) + ) + return await self._blast_message( + stream_ids, + message_object.content, + message_object.data, + message_object.version, + message_object.attachments, + message_object.previews, + ) + @retry async def _blast_message( - self, - stream_ids: List[str], - message: str, - data: str = "", - version: str = "", - attachment: List[IO] = None, - preview: List[IO] = None + self, + stream_ids: List[str], + message: str, + data: str = "", + version: str = "", + attachment: List[IO] = None, + preview: List[IO] = None, ) -> V4MessageBlastResponse: """Send a message to multiple existing streams. See: `Blast Message `_ @@ -234,7 +272,7 @@ async def _blast_message( "key_manager_token": await self._auth_session.key_manager_token, "message": message, "data": data, - "version": version + "version": version, } if attachment is not None: params["attachment"] = attachment if isinstance(attachment, list) else [attachment] @@ -245,18 +283,20 @@ async def _blast_message( class MessageService(OboMessageService): - """Service class for managing messages. - """ - - def __init__(self, messages_api: MultiAttachmentsMessagesApi, - message_api: MessageApi, - message_suppression_api: MessageSuppressionApi, - streams_api: StreamsApi, - pod_api: PodApi, - attachment_api: AttachmentsApi, - default_api: DefaultApi, - auth_session: AuthSession, - retry_config: BdkRetryConfig): + """Service class for managing messages.""" + + def __init__( + self, + messages_api: MultiAttachmentsMessagesApi, + message_api: MessageApi, + message_suppression_api: MessageSuppressionApi, + streams_api: StreamsApi, + pod_api: PodApi, + attachment_api: AttachmentsApi, + default_api: DefaultApi, + auth_session: AuthSession, + retry_config: BdkRetryConfig, + ): super().__init__(messages_api, message_suppression_api, pod_api, auth_session, retry_config) self._message_api = message_api self._message_suppression_api = message_suppression_api @@ -266,11 +306,7 @@ def __init__(self, messages_api: MultiAttachmentsMessagesApi, @retry async def list_messages( - self, - stream_id: str, - since: int = 0, - skip: int = 0, - limit: int = 50 + self, stream_id: str, since: int = 0, skip: int = 0, limit: int = 50 ) -> [V4Message]: """Get messages from an existing stream. Additionally returns any attachments associated with the message. See: `Messages `_ @@ -289,16 +325,13 @@ async def list_messages( "session_token": await self._auth_session.session_token, "key_manager_token": await self._auth_session.key_manager_token, "skip": skip, - "limit": limit + "limit": limit, } message_list = await self._messages_api.v4_stream_sid_message_get(**params) return message_list.value @retry - async def import_messages( - self, - messages: List[V4ImportedMessage] - ) -> [V4ImportResponse]: + async def import_messages(self, messages: List[V4ImportedMessage]) -> [V4ImportResponse]: """Imports a list of messages to Symphony. See: `Import Message `_ @@ -310,17 +343,12 @@ async def import_messages( params = { "message_list": V4MessageImportList(value=messages), "session_token": await self._auth_session.session_token, - "key_manager_token": await self._auth_session.key_manager_token + "key_manager_token": await self._auth_session.key_manager_token, } import_response_list = await self._messages_api.v4_message_import_post(**params) return import_response_list.value - async def get_attachment( - self, - stream_id: str, - message_id: str, - attachment_id: str - ) -> str: + async def get_attachment(self, stream_id: str, message_id: str, attachment_id: str) -> str: """Downloads the attachment body by the stream ID, message ID and attachment ID. See: `Attachment `_ @@ -336,14 +364,11 @@ async def get_attachment( "file_id": attachment_id, "message_id": message_id, "session_token": await self._auth_session.session_token, - "key_manager_token": await self._auth_session.key_manager_token + "key_manager_token": await self._auth_session.key_manager_token, } return await self._attachment_api.v1_stream_sid_attachment_get(**params) - async def get_message_status( - self, - message_id: str - ) -> MessageStatus: + async def get_message_status(self, message_id: str) -> MessageStatus: """Get the status of a particular message, i.e the list of users who the message was sent to, delivered to and the list of users who read the message. See: `Message Status `_ @@ -355,15 +380,12 @@ async def get_message_status( """ params = { "mid": message_id, - "session_token": await self._auth_session.session_token + "session_token": await self._auth_session.session_token, } return await self._message_api.v1_message_mid_status_get(**params) @retry - async def get_message( - self, - message_id: str - ) -> V4Message: + async def get_message(self, message_id: str) -> V4Message: """Retrieves the details of a message given its message ID. See: `Get Message `_ @@ -375,18 +397,18 @@ async def get_message( params = { "id": message_id, "session_token": await self._auth_session.session_token, - "key_manager_token": await self._auth_session.key_manager_token + "key_manager_token": await self._auth_session.key_manager_token, } return await self._messages_api.v1_message_id_get(**params) @retry async def list_attachments( - self, - stream_id: str, - since: int = None, - to: int = None, - limit: int = 50, - sort_dir: str = "ASC" + self, + stream_id: str, + since: int = None, + to: int = None, + limit: int = 50, + sort_dir: str = "ASC", ) -> List[StreamAttachmentItem]: """List attachments in a particular stream. See: `List Attachments `_ @@ -405,7 +427,7 @@ async def list_attachments( "sid": stream_id, "session_token": await self._auth_session.session_token, "limit": limit, - "sort_dir": sort_dir + "sort_dir": sort_dir, } if since is not None: params["since"] = since @@ -415,10 +437,7 @@ async def list_attachments( return attachment_list.value @retry - async def list_message_receipts( - self, - message_id: str - ) -> MessageReceiptDetailResponse: + async def list_message_receipts(self, message_id: str) -> MessageReceiptDetailResponse: """Fetches receipts details from a specific message. See: `List Message Receipts `_ @@ -429,15 +448,12 @@ async def list_message_receipts( """ params = { "message_id": message_id, - "session_token": await self._auth_session.session_token + "session_token": await self._auth_session.session_token, } return await self._default_api.v1_admin_messages_message_id_receipts_get(**params) @retry - async def get_message_relationships( - self, - message_id: str - ) -> MessageMetadataResponse: + async def get_message_relationships(self, message_id: str) -> MessageMetadataResponse: """Gets the message metadata relationship. This API allows users to track the relationship between a message and all the forwards and replies of that message. @@ -451,13 +467,20 @@ async def get_message_relationships( params = { "message_id": message_id, "session_token": await self._auth_session.session_token, - "user_agent": "" + "user_agent": "", } - return await self._default_api.v1_admin_messages_message_id_metadata_relationships_get(**params) + return await self._default_api.v1_admin_messages_message_id_metadata_relationships_get( + **params + ) @retry - async def search_messages(self, query: MessageSearchQuery, sort_dir: str = "desc", skip: int = 0, - limit: int = 50) -> List[V4Message]: + async def search_messages( + self, + query: MessageSearchQuery, + sort_dir: str = "desc", + skip: int = 0, + limit: int = 50, + ) -> List[V4Message]: """Searches for messages in the context of a specified user, given an argument-based query and pagination attributes (skip and limit parameters). See: `Message Search (using POST) `_ @@ -475,13 +498,18 @@ async def search_messages(self, query: MessageSearchQuery, sort_dir: str = "desc "query": query, "sort_dir": sort_dir, "skip": skip, - "limit": limit + "limit": limit, } message_list = await self._messages_api.v1_message_search_post(**params) return message_list.value # endpoint returns empty list when no values found - async def search_all_messages(self, query: MessageSearchQuery, sort_dir: str = "desc", chunk_size: int = 50, - max_number: int = None) -> AsyncGenerator[V4Message, None]: + async def search_all_messages( + self, + query: MessageSearchQuery, + sort_dir: str = "desc", + chunk_size: int = 50, + max_number: int = None, + ) -> AsyncGenerator[V4Message, None]: """Searches for messages in the context of a specified user, given an argument-based query. See: `Message Search (using POST) `_ @@ -500,11 +528,19 @@ async def search_messages_one_page(skip, limit): @staticmethod def _validate_message_search_query(query: MessageSearchQuery): # Check streamType value among accepted ones if specified - if query.stream_type is not None and query.stream_type not in ["CHAT", "IM", "MIM", "ROOM", "POST"]: - raise ValueError(f"Wrong stream type {query.stream_type}. " - f"Accepted values are: CHAT (1-1 instant messages and multi-party instant messages), " - f"IM (1-1 instant message), MIM (multi-party instant message), " - f"ROOM or POST (user profile wall posts)") + if query.stream_type is not None and query.stream_type not in [ + "CHAT", + "IM", + "MIM", + "ROOM", + "POST", + ]: + raise ValueError( + f"Wrong stream type {query.stream_type}. " + f"Accepted values are: CHAT (1-1 instant messages and multi-party instant messages), " + f"IM (1-1 instant message), MIM (multi-party instant message), " + f"ROOM or POST (user profile wall posts)" + ) # Text queries require streamId to be provided if query.text is not None and query.stream_id is None: diff --git a/symphony/bdk/core/service/message/messageml_util.py b/symphony/bdk/core/service/message/messageml_util.py index 3839f5f6..e1e4441e 100644 --- a/symphony/bdk/core/service/message/messageml_util.py +++ b/symphony/bdk/core/service/message/messageml_util.py @@ -1,10 +1,10 @@ -"""This module handles pre-processing for an outgoing message in order to have a valid messageML format. -""" +"""This module handles pre-processing for an outgoing message in order to have a valid messageML format.""" + import re def escape_special_chars(raw_text: str) -> str: - """ Replace all special characters placed within the messageML that must be HTML-escaped + """Replace all special characters placed within the messageML that must be HTML-escaped to have a valid MessageML format. :param raw_text: text to be parsed @@ -14,7 +14,7 @@ def escape_special_chars(raw_text: str) -> str: parsed_text = "" matches = re.finditer(_pattern(), raw_text) for match in matches: - parsed_text += raw_text[current_parsed_index:match.start()] + _replacement(match.group(0)) + parsed_text += raw_text[current_parsed_index : match.start()] + _replacement(match.group(0)) current_parsed_index = match.end() return parsed_text + raw_text[current_parsed_index:] @@ -32,7 +32,7 @@ def _replacement(match): "<": "<", ">": ">", "'": "'", - "\"": """, + '"': """, "#": "#", "\\$": "$", "%": "%", @@ -47,5 +47,5 @@ def _replacement(match): "\\]": "]", "`": "`", "\\{": "{", - "\\}": "}" + "\\}": "}", } diff --git a/symphony/bdk/core/service/message/model.py b/symphony/bdk/core/service/message/model.py index a13a0b4b..573f9a86 100644 --- a/symphony/bdk/core/service/message/model.py +++ b/symphony/bdk/core/service/message/model.py @@ -1,5 +1,5 @@ import json -from typing import List, Tuple, Union, IO +from typing import IO, List, Tuple, Union from symphony.bdk.core.service.exception import MessageCreationError @@ -14,8 +14,14 @@ class Message: `Create Message `_. """ - def __init__(self, content: str, data=None, silent=True, - attachments: List[Union[IO, Tuple[IO, IO]]] = None, version: str = ""): + def __init__( + self, + content: str, + data=None, + silent=True, + attachments: List[Union[IO, Tuple[IO, IO]]] = None, + version: str = "", + ): """Builds a message. :param content: the MessageML content to be sent. This is mandatory @@ -89,7 +95,9 @@ def previews(self) -> List[IO]: def _get_content(content): if content is None: raise MessageCreationError("Message content is mandatory") - if not content.startswith(MESSAGE_ML_START_TAG) and not content.endswith(MESSAGE_ML_END_TAG): + if not content.startswith(MESSAGE_ML_START_TAG) and not content.endswith( + MESSAGE_ML_END_TAG + ): return MESSAGE_ML_START_TAG + content + MESSAGE_ML_END_TAG return content @@ -109,6 +117,8 @@ def _get_attachments_and_previews(attachments_previews): attachments.append(item) if previews and len(previews) != len(attachments): - raise MessageCreationError("Message should contain either no preview or as many previews as attachments") + raise MessageCreationError( + "Message should contain either no preview or as many previews as attachments" + ) return attachments, previews diff --git a/symphony/bdk/core/service/message/multi_attachments_messages_api.py b/symphony/bdk/core/service/message/multi_attachments_messages_api.py index 1c8ed0a9..6a27f29c 100644 --- a/symphony/bdk/core/service/message/multi_attachments_messages_api.py +++ b/symphony/bdk/core/service/message/multi_attachments_messages_api.py @@ -2,9 +2,7 @@ from symphony.bdk.gen.agent_model.v4_message import V4Message from symphony.bdk.gen.agent_model.v4_message_blast_response import V4MessageBlastResponse from symphony.bdk.gen.api_client import Endpoint -from symphony.bdk.gen.model_utils import ( - file_type -) +from symphony.bdk.gen.model_utils import file_type class MultiAttachmentsMessagesApi(MessagesApi): @@ -17,186 +15,140 @@ def __init__(self, api_client=None): self.v4_multi_attachment_message_blast_post_endpoint = Endpoint( settings={ - 'response_type': (V4MessageBlastResponse,), - 'auth': [], - 'endpoint_path': '/v4/message/blast', - 'operation_id': 'v4_message_blast_post', - 'http_method': 'POST', - 'servers': None, + "response_type": (V4MessageBlastResponse,), + "auth": [], + "endpoint_path": "/v4/message/blast", + "operation_id": "v4_message_blast_post", + "http_method": "POST", + "servers": None, }, params_map={ - 'all': [ - 'session_token', - 'sids', - 'key_manager_token', - 'message', - 'data', - 'version', - 'attachment', - 'preview', + "all": [ + "session_token", + "sids", + "key_manager_token", + "message", + "data", + "version", + "attachment", + "preview", ], - 'required': [ - 'session_token', - 'sids', + "required": [ + "session_token", + "sids", ], - 'nullable': [ - ], - 'enum': [ - ], - 'validation': [ - ] + "nullable": [], + "enum": [], + "validation": [], }, root_map={ - 'validations': { - }, - 'allowed_values': { + "validations": {}, + "allowed_values": {}, + "openapi_types": { + "session_token": (str,), + "sids": ([str],), + "key_manager_token": (str,), + "message": (str,), + "data": (str,), + "version": (str,), + "attachment": ([file_type],), + "preview": ([file_type],), }, - 'openapi_types': { - 'session_token': - (str,), - 'sids': - ([str],), - 'key_manager_token': - (str,), - 'message': - (str,), - 'data': - (str,), - 'version': - (str,), - 'attachment': - ([file_type],), - 'preview': - ([file_type],), + "attribute_map": { + "session_token": "sessionToken", + "sids": "sids", + "key_manager_token": "keyManagerToken", + "message": "message", + "data": "data", + "version": "version", + "attachment": "attachment", + "preview": "preview", }, - 'attribute_map': { - 'session_token': 'sessionToken', - 'sids': 'sids', - 'key_manager_token': 'keyManagerToken', - 'message': 'message', - 'data': 'data', - 'version': 'version', - 'attachment': 'attachment', - 'preview': 'preview', + "location_map": { + "session_token": "header", + "sids": "form", + "key_manager_token": "header", + "message": "form", + "data": "form", + "version": "form", + "attachment": "form", + "preview": "form", }, - 'location_map': { - 'session_token': 'header', - 'sids': 'form', - 'key_manager_token': 'header', - 'message': 'form', - 'data': 'form', - 'version': 'form', - 'attachment': 'form', - 'preview': 'form', + "collection_format_map": { + "sids": "csv", }, - 'collection_format_map': { - 'sids': 'csv', - } }, - headers_map={ - 'accept': [ - 'application/json' - ], - 'content_type': [ - 'multipart/form-data' - ] - }, - api_client=api_client + headers_map={"accept": ["application/json"], "content_type": ["multipart/form-data"]}, + api_client=api_client, ) self.v4_stream_sid_multi_attachment_message_create_post_endpoint = Endpoint( settings={ - 'response_type': (V4Message,), - 'auth': [], - 'endpoint_path': '/v4/stream/{sid}/message/create', - 'operation_id': 'v4_stream_sid_message_create_post', - 'http_method': 'POST', - 'servers': None, + "response_type": (V4Message,), + "auth": [], + "endpoint_path": "/v4/stream/{sid}/message/create", + "operation_id": "v4_stream_sid_message_create_post", + "http_method": "POST", + "servers": None, }, params_map={ - 'all': [ - 'sid', - 'session_token', - 'key_manager_token', - 'message', - 'data', - 'version', - 'attachment', - 'preview', - ], - 'required': [ - 'sid', - 'session_token', + "all": [ + "sid", + "session_token", + "key_manager_token", + "message", + "data", + "version", + "attachment", + "preview", ], - 'nullable': [ + "required": [ + "sid", + "session_token", ], - 'enum': [ - ], - 'validation': [ - ] + "nullable": [], + "enum": [], + "validation": [], }, root_map={ - 'validations': { - }, - 'allowed_values': { - }, - 'openapi_types': { - 'sid': - (str,), - 'session_token': - (str,), - 'key_manager_token': - (str,), - 'message': - (str,), - 'data': - (str,), - 'version': - (str,), - 'attachment': - ([file_type],), - 'preview': - ([file_type],), + "validations": {}, + "allowed_values": {}, + "openapi_types": { + "sid": (str,), + "session_token": (str,), + "key_manager_token": (str,), + "message": (str,), + "data": (str,), + "version": (str,), + "attachment": ([file_type],), + "preview": ([file_type],), }, - 'attribute_map': { - 'sid': 'sid', - 'session_token': 'sessionToken', - 'key_manager_token': 'keyManagerToken', - 'message': 'message', - 'data': 'data', - 'version': 'version', - 'attachment': 'attachment', - 'preview': 'preview', + "attribute_map": { + "sid": "sid", + "session_token": "sessionToken", + "key_manager_token": "keyManagerToken", + "message": "message", + "data": "data", + "version": "version", + "attachment": "attachment", + "preview": "preview", }, - 'location_map': { - 'sid': 'path', - 'session_token': 'header', - 'key_manager_token': 'header', - 'message': 'form', - 'data': 'form', - 'version': 'form', - 'attachment': 'form', - 'preview': 'form', + "location_map": { + "sid": "path", + "session_token": "header", + "key_manager_token": "header", + "message": "form", + "data": "form", + "version": "form", + "attachment": "form", + "preview": "form", }, - 'collection_format_map': { - } + "collection_format_map": {}, }, - headers_map={ - 'accept': [ - 'application/json' - ], - 'content_type': [ - 'multipart/form-data' - ] - }, - api_client=api_client + headers_map={"accept": ["application/json"], "content_type": ["multipart/form-data"]}, + api_client=api_client, ) - def v4_stream_sid_multi_attachment_message_create_post( - self, - sid, - session_token, - **kwargs - ): + def v4_stream_sid_multi_attachment_message_create_post(self, sid, session_token, **kwargs): """Post a message to one existing stream. # noqa: E501 Post a new message to the given stream. The stream can be a chatroom, an IM or a multiparty IM. You may include an attachment on the message. The message can be provided as MessageMLV2 or PresentationML. Both formats support Freemarker templates. The optional parameter \"data\" can be used to provide a JSON payload containing entity data. If the message contains explicit references to entity data (in \"data-entity-id\" element attributes), this parameter is required. If the message is in MessageML and fails schema validation a client error results If the message is sent then 200 is returned. Regarding authentication, you must either use the sessionToken which was created for delegated app access or both the sessionToken and keyManagerToken together. # noqa: E501 @@ -249,42 +201,22 @@ def v4_stream_sid_multi_attachment_message_create_post( If the method is called asynchronously, returns the request thread. """ - kwargs['async_req'] = kwargs.get( - 'async_req', False - ) - kwargs['_return_http_data_only'] = kwargs.get( - '_return_http_data_only', True + kwargs["async_req"] = kwargs.get("async_req", False) + kwargs["_return_http_data_only"] = kwargs.get("_return_http_data_only", True) + kwargs["_preload_content"] = kwargs.get("_preload_content", True) + kwargs["_request_timeout"] = kwargs.get("_request_timeout", None) + kwargs["_check_input_type"] = kwargs.get("_check_input_type", True) + kwargs["_check_return_type"] = kwargs.get("_check_return_type", True) + kwargs["_spec_property_naming"] = kwargs.get("_spec_property_naming", False) + kwargs["_content_type"] = kwargs.get("_content_type") + kwargs["_host_index"] = kwargs.get("_host_index") + kwargs["sid"] = sid + kwargs["session_token"] = session_token + return self.v4_stream_sid_multi_attachment_message_create_post_endpoint.call_with_http_info( + **kwargs ) - kwargs['_preload_content'] = kwargs.get( - '_preload_content', True - ) - kwargs['_request_timeout'] = kwargs.get( - '_request_timeout', None - ) - kwargs['_check_input_type'] = kwargs.get( - '_check_input_type', True - ) - kwargs['_check_return_type'] = kwargs.get( - '_check_return_type', True - ) - kwargs['_spec_property_naming'] = kwargs.get( - '_spec_property_naming', False - ) - kwargs['_content_type'] = kwargs.get( - '_content_type') - kwargs['_host_index'] = kwargs.get('_host_index') - kwargs['sid'] = \ - sid - kwargs['session_token'] = \ - session_token - return self.v4_stream_sid_multi_attachment_message_create_post_endpoint.call_with_http_info(**kwargs) - def v4_multi_attachment_message_blast_post( - self, - session_token, - sids, - **kwargs - ): + def v4_multi_attachment_message_blast_post(self, session_token, sids, **kwargs): """Post a message to multiple existing streams. # noqa: E501 Post a new message to the given list of streams. The stream can be a chatroom, an IM or a multiparty IM. You may include an attachment on the message. The message can be provided as MessageMLV2 or PresentationML. Both formats support Freemarker templates. The optional parameter \"data\" can be used to provide a JSON payload containing entity data. If the message contains explicit references to entity data (in \"data-entity-id\" element attributes), this parameter is required. If the message is in MessageML and fails schema validation a client error results This endpoint is idempotent, it means that a 200 response will be returned even if the message has not been delivered to some streams. Check the `errors` map from the response in order to see on which stream(s) the message has not been delivered. The maximum number of streams where the message can be sent is limitted to 100. Regarding authentication, you must either use the sessionToken which was created for delegated app access or both the sessionToken and keyManagerToken together. # noqa: E501 @@ -337,32 +269,15 @@ def v4_multi_attachment_message_blast_post( If the method is called asynchronously, returns the request thread. """ - kwargs['async_req'] = kwargs.get( - 'async_req', False - ) - kwargs['_return_http_data_only'] = kwargs.get( - '_return_http_data_only', True - ) - kwargs['_preload_content'] = kwargs.get( - '_preload_content', True - ) - kwargs['_request_timeout'] = kwargs.get( - '_request_timeout', None - ) - kwargs['_check_input_type'] = kwargs.get( - '_check_input_type', True - ) - kwargs['_check_return_type'] = kwargs.get( - '_check_return_type', True - ) - kwargs['_spec_property_naming'] = kwargs.get( - '_spec_property_naming', False - ) - kwargs['_content_type'] = kwargs.get( - '_content_type') - kwargs['_host_index'] = kwargs.get('_host_index') - kwargs['session_token'] = \ - session_token - kwargs['sids'] = \ - sids + kwargs["async_req"] = kwargs.get("async_req", False) + kwargs["_return_http_data_only"] = kwargs.get("_return_http_data_only", True) + kwargs["_preload_content"] = kwargs.get("_preload_content", True) + kwargs["_request_timeout"] = kwargs.get("_request_timeout", None) + kwargs["_check_input_type"] = kwargs.get("_check_input_type", True) + kwargs["_check_return_type"] = kwargs.get("_check_return_type", True) + kwargs["_spec_property_naming"] = kwargs.get("_spec_property_naming", False) + kwargs["_content_type"] = kwargs.get("_content_type") + kwargs["_host_index"] = kwargs.get("_host_index") + kwargs["session_token"] = session_token + kwargs["sids"] = sids return self.v4_multi_attachment_message_blast_post_endpoint.call_with_http_info(**kwargs) diff --git a/symphony/bdk/core/service/obo_services.py b/symphony/bdk/core/service/obo_services.py index ee27e953..8018e70c 100644 --- a/symphony/bdk/core/service/obo_services.py +++ b/symphony/bdk/core/service/obo_services.py @@ -11,7 +11,7 @@ class OboServices: """Entry point for OBO-enabled services, see - `the list of OBO-enabled endpoints `_ + `the list of OBO-enabled endpoints `_ """ async def __aenter__(self): @@ -30,7 +30,9 @@ def __init__(self, config: BdkConfig, obo_session: OboAuthSession): self._obo_session = obo_session self._api_client_factory = ApiClientFactory(config) - self._service_factory = OboServiceFactory(self._api_client_factory, self._obo_session, self._config) + self._service_factory = OboServiceFactory( + self._api_client_factory, self._obo_session, self._config + ) self._connection_service = self._service_factory.get_connection_service() self._message_service = self._service_factory.get_message_service() self._stream_service = self._service_factory.get_stream_service() @@ -73,6 +75,5 @@ def signals(self) -> OboSignalService: return self._signal_service async def close_clients(self): - """Close all the existing api clients created by the api client factory. - """ + """Close all the existing api clients created by the api client factory.""" await self._api_client_factory.close_clients() diff --git a/symphony/bdk/core/service/pagination.py b/symphony/bdk/core/service/pagination.py index d24cd09c..434e9814 100644 --- a/symphony/bdk/core/service/pagination.py +++ b/symphony/bdk/core/service/pagination.py @@ -1,13 +1,15 @@ """This module takes care of creating generators from paginated endpoints, so that user do not have to care about making several calls to the same endpoint with the correct pagination values. """ -from typing import AsyncGenerator, TypeVar, Callable, Awaitable, Tuple -T = TypeVar('T') +from typing import AsyncGenerator, Awaitable, Callable, Tuple, TypeVar +T = TypeVar("T") -async def offset_based_pagination(func: Callable[[int, int], Awaitable[T]], - chunk_size=50, max_number=None) -> AsyncGenerator[T, None]: + +async def offset_based_pagination( + func: Callable[[int, int], Awaitable[T]], chunk_size=50, max_number=None +) -> AsyncGenerator[T, None]: """Creates an asynchronous generator from a paginated endpoint. The generator makes the call to the underlying endpoint `func` until the `max_number` of items is reached or results are exhausted (i.e. `func` is None or empty). @@ -42,8 +44,9 @@ async def offset_based_pagination(func: Callable[[int, int], Awaitable[T]], chunk = await func(skip, chunk_size) -async def cursor_based_pagination(func: Callable[[int, str], Awaitable[Tuple[T, str]]], - chunk_size=100, max_number=None) -> AsyncGenerator[T, None]: +async def cursor_based_pagination( + func: Callable[[int, str], Awaitable[Tuple[T, str]]], chunk_size=100, max_number=None +) -> AsyncGenerator[T, None]: """Creates an asynchronous generator from a cursor based endpoint. The generator makes the call to the underlying endpoint `func` until the `max_number` of items is reached or results are exhausted (i.e. cursor returned by `func` is None). diff --git a/symphony/bdk/core/service/presence/presence_service.py b/symphony/bdk/core/service/presence/presence_service.py index 7729319c..2f12c5ad 100644 --- a/symphony/bdk/core/service/presence/presence_service.py +++ b/symphony/bdk/core/service/presence/presence_service.py @@ -38,22 +38,26 @@ class OboPresenceService: * Delete a created presence feed """ - def __init__(self, presence_api: PresenceApi, auth_session: AuthSession, retry_config: BdkRetryConfig): + def __init__( + self, presence_api: PresenceApi, auth_session: AuthSession, retry_config: BdkRetryConfig + ): self._presence_api = presence_api self._auth_session = auth_session self._retry_config = retry_config @retry async def get_presence(self) -> V2Presence: - """ Get the online status (presence info) of the calling user. + """Get the online status (presence info) of the calling user. See: `Get Presence `_. :return: Presence info of the calling user. """ - return await self._presence_api.v2_user_presence_get(session_token=await self._auth_session.session_token) + return await self._presence_api.v2_user_presence_get( + session_token=await self._auth_session.session_token + ) async def get_all_presence(self, last_user_id: int, limit: int) -> List[V2Presence]: - """ Get the presence info of all users in a pod. + """Get the presence info of all users in a pod. See: `Get All Presence `_. :param last_user_id: Last user ID retrieved, used for paging. If provided, results skip users with IDs less @@ -64,12 +68,13 @@ async def get_all_presence(self, last_user_id: int, limit: int) -> List[V2Presen presence_list = await self._presence_api.v2_users_presence_get( session_token=await self._auth_session.session_token, last_user_id=last_user_id, - limit=limit) + limit=limit, + ) return presence_list.value @retry async def get_user_presence(self, user_id: int, local: bool) -> V2Presence: - """ Get the presence info of a specified user. + """Get the presence info of a specified user. See: `Get User Presence `_. :param user_id: User Id @@ -78,24 +83,25 @@ async def get_user_presence(self, user_id: int, local: bool) -> V2Presence: connected to the calling user. :return: Presence info of the looked up user. """ - return await self._presence_api.v3_user_uid_presence_get(uid=user_id, - session_token=await self._auth_session.session_token, - local=local) + return await self._presence_api.v3_user_uid_presence_get( + uid=user_id, session_token=await self._auth_session.session_token, local=local + ) @retry async def external_presence_interest(self, user_ids: List[int]): - """ Register interest in a list of external users to get their presence info. + """Register interest in a list of external users to get their presence info. See: `External Presence Interest `_. :param user_ids: List of user ids to be registered. """ - await self._presence_api.v1_user_presence_register_post(session_token=await self._auth_session.session_token, - uid_list=user_ids) + await self._presence_api.v1_user_presence_register_post( + session_token=await self._auth_session.session_token, uid_list=user_ids + ) @retry async def set_presence(self, status: PresenceStatus, soft: bool) -> V2Presence: - """ Set the presence info of the calling user. + """Set the presence info of the calling user. See: `Set Presence `_. :param status: The new presence state for the user. @@ -108,13 +114,15 @@ async def set_presence(self, status: PresenceStatus, soft: bool) -> V2Presence: :return: Presence info of the calling user. """ presence_status: V2PresenceStatus = V2PresenceStatus(category=status.name) - return await self._presence_api.v2_user_presence_post(session_token=await self._auth_session.session_token, - presence=presence_status, - soft=soft) + return await self._presence_api.v2_user_presence_post( + session_token=await self._auth_session.session_token, + presence=presence_status, + soft=soft, + ) @retry async def create_presence_feed(self) -> str: - """ Creates a new stream capturing online status changes ("presence feed") for the company (pod) and returns + """Creates a new stream capturing online status changes ("presence feed") for the company (pod) and returns the ID of the new feed. The feed will return the presence of users whose presence status has changed since it was last read. See: `Create Presence Feed `_. @@ -122,12 +130,13 @@ async def create_presence_feed(self) -> str: :return: Presence feed Id """ string_id = await self._presence_api.v1_presence_feed_create_post( - session_token=await self._auth_session.session_token) + session_token=await self._auth_session.session_token + ) return string_id.id @retry async def read_presence_feed(self, feed_id: str) -> List[V2Presence]: - """ Reads the specified presence feed that was created. + """Reads the specified presence feed that was created. The feed returned includes the user presence statuses that have changed since they were last read. See: `Read Presence Feed `_. @@ -135,25 +144,28 @@ async def read_presence_feed(self, feed_id: str) -> List[V2Presence]: :return: The list of user presences has changed since the last presence read. """ presence_list = await self._presence_api.v1_presence_feed_feed_id_read_get( - session_token=await self._auth_session.session_token, - feed_id=feed_id) + session_token=await self._auth_session.session_token, feed_id=feed_id + ) return presence_list.value @retry async def delete_presence_feed(self, feed_id: str) -> str: - """ Delete the specified presence feed that was created. + """Delete the specified presence feed that was created. See: `Delete Presence Feed `_. :param feed_id: The presence feed id to be deleted. :return: The id of the deleted presence feed. """ string_id = await self._presence_api.v1_presence_feed_feed_id_delete_post( - session_token=await self._auth_session.session_token, feed_id=feed_id) + session_token=await self._auth_session.session_token, feed_id=feed_id + ) return string_id.id @retry - async def set_user_presence(self, user_id: int, status: PresenceStatus, soft: bool) -> V2Presence: - """ Set the presence state of a another user. + async def set_user_presence( + self, user_id: int, status: PresenceStatus, soft: bool + ) -> V2Presence: + """Set the presence state of a another user. See: `Set Other User's Presence - Admin V3 `_. @@ -168,9 +180,9 @@ async def set_user_presence(self, user_id: int, status: PresenceStatus, soft: bo :return: The presence info of the specified user. """ user_presence: V2UserPresence = V2UserPresence(category=status.name, user_id=user_id) - return await self._presence_api.v3_user_presence_post(session_token=await self._auth_session.session_token, - presence=user_presence, - soft=soft) + return await self._presence_api.v3_user_presence_post( + session_token=await self._auth_session.session_token, presence=user_presence, soft=soft + ) class PresenceService(OboPresenceService): diff --git a/symphony/bdk/core/service/session/session_service.py b/symphony/bdk/core/service/session/session_service.py index 77a700ae..a52962ad 100644 --- a/symphony/bdk/core/service/session/session_service.py +++ b/symphony/bdk/core/service/session/session_service.py @@ -6,31 +6,25 @@ class OboSessionService: - """Service interface exposing OBO-enabled endpoints to get user session information. - """ + """Service interface exposing OBO-enabled endpoints to get user session information.""" - def __init__(self, session_api: SessionApi, - auth_session: AuthSession, - retry_config: BdkRetryConfig): + def __init__( + self, session_api: SessionApi, auth_session: AuthSession, retry_config: BdkRetryConfig + ): self._session_api = session_api self._auth_session = auth_session self._retry_config = retry_config @retry - async def get_session( - self - ) -> UserV2: + async def get_session(self) -> UserV2: """ Retrieves the {@link UserV2} session from the pod using an {@link AuthSession} holder. Returns: User session info """ - params = { - 'session_token': await self._auth_session.session_token - } + params = {"session_token": await self._auth_session.session_token} return await self._session_api.v2_sessioninfo_get(**params) class SessionService(OboSessionService): - """Service class for exposing endpoints to get user session information. - """ + """Service class for exposing endpoints to get user session information.""" diff --git a/symphony/bdk/core/service/signal/signal_service.py b/symphony/bdk/core/service/signal/signal_service.py index 03f1a4f8..6ea62eac 100644 --- a/symphony/bdk/core/service/signal/signal_service.py +++ b/symphony/bdk/core/service/signal/signal_service.py @@ -27,7 +27,9 @@ class OboSignalService: * Subscribe or unsubscribe a signal """ - def __init__(self, signals_api: SignalsApi, auth_session: AuthSession, retry_config: BdkRetryConfig): + def __init__( + self, signals_api: SignalsApi, auth_session: AuthSession, retry_config: BdkRetryConfig + ): self._signals_api = signals_api self._auth_session = auth_session self._retry_config = retry_config @@ -45,10 +47,15 @@ async def list_signals(self, skip: int = 0, limit: int = 50) -> SignalList: """ return await self._signals_api.v1_signals_list_get( - skip=skip, limit=limit, session_token=await self._auth_session.session_token, - key_manager_token=await self._auth_session.key_manager_token) - - async def list_all_signals(self, chunk_size: int = 50, max_number: int = None) -> AsyncGenerator[Signal, None]: + skip=skip, + limit=limit, + session_token=await self._auth_session.session_token, + key_manager_token=await self._auth_session.key_manager_token, + ) + + async def list_all_signals( + self, chunk_size: int = 50, max_number: int = None + ) -> AsyncGenerator[Signal, None]: """Lists all signals on behalf of the user. The response includes signals that the user has created and public signals to which they have subscribed. @@ -67,7 +74,7 @@ async def list_signals_one_page(skip, limit): @retry async def get_signal(self, signal_id: str) -> Signal: - """ Gets details about the specified signal. + """Gets details about the specified signal. See: 'Get signal '_ @@ -76,12 +83,14 @@ async def get_signal(self, signal_id: str) -> Signal: """ return await self._signals_api.v1_signals_id_get_get( - id=signal_id, session_token=await self._auth_session.session_token, - key_manager_token=await self._auth_session.key_manager_token) + id=signal_id, + session_token=await self._auth_session.session_token, + key_manager_token=await self._auth_session.key_manager_token, + ) @retry async def create_signal(self, signal: BaseSignal) -> Signal: - """ Creates a new Signal. + """Creates a new Signal. See: 'Create signal '_ @@ -90,12 +99,14 @@ async def create_signal(self, signal: BaseSignal) -> Signal: """ return await self._signals_api.v1_signals_create_post( - signal=signal, session_token=await self._auth_session.session_token, - key_manager_token=await self._auth_session.key_manager_token) + signal=signal, + session_token=await self._auth_session.session_token, + key_manager_token=await self._auth_session.key_manager_token, + ) @retry async def update_signal(self, signal_id: str, signal: BaseSignal) -> Signal: - """ Updates an existing Signal. + """Updates an existing Signal. See: 'Update signal '_ @@ -105,26 +116,32 @@ async def update_signal(self, signal_id: str, signal: BaseSignal) -> Signal: """ return await self._signals_api.v1_signals_id_update_post( - id=signal_id, signal=signal, session_token=await self._auth_session.session_token, - key_manager_token=await self._auth_session.key_manager_token) + id=signal_id, + signal=signal, + session_token=await self._auth_session.session_token, + key_manager_token=await self._auth_session.key_manager_token, + ) @retry async def delete_signal(self, signal_id: str) -> None: - """ Deletes an existing Signal. + """Deletes an existing Signal. See: 'Delete signal '_ :param signal_id: The Id of the existing signal to be deleted. """ - await self._signals_api.v1_signals_id_delete_post(id=signal_id, - session_token=await self._auth_session.session_token, - key_manager_token=await self._auth_session.key_manager_token) + await self._signals_api.v1_signals_id_delete_post( + id=signal_id, + session_token=await self._auth_session.session_token, + key_manager_token=await self._auth_session.key_manager_token, + ) @retry - async def subscribe_users_to_signal(self, signal_id: str, pushed: bool, - user_ids: [int]) -> ChannelSubscriptionResponse: - """ Subscribe an array of users to a Signal. + async def subscribe_users_to_signal( + self, signal_id: str, pushed: bool, user_ids: [int] + ) -> ChannelSubscriptionResponse: + """Subscribe an array of users to a Signal. See: 'Subscribe signal '_ @@ -135,12 +152,18 @@ async def subscribe_users_to_signal(self, signal_id: str, pushed: bool, """ return await self._signals_api.v1_signals_id_subscribe_post( - id=signal_id, pushed=pushed, users=user_ids, session_token=await self._auth_session.session_token, - key_manager_token=await self._auth_session.key_manager_token) + id=signal_id, + pushed=pushed, + users=user_ids, + session_token=await self._auth_session.session_token, + key_manager_token=await self._auth_session.key_manager_token, + ) @retry - async def unsubscribe_users_to_signal(self, signal_id: str, user_ids: [int]) -> ChannelSubscriptionResponse: - """ Unsubscribes an array of users from the specified Signal. + async def unsubscribe_users_to_signal( + self, signal_id: str, user_ids: [int] + ) -> ChannelSubscriptionResponse: + """Unsubscribes an array of users from the specified Signal. See: 'Unsubscribe signal '_ @@ -150,11 +173,16 @@ async def unsubscribe_users_to_signal(self, signal_id: str, user_ids: [int]) -> """ return await self._signals_api.v1_signals_id_unsubscribe_post( - id=signal_id, users=user_ids, session_token=await self._auth_session.session_token, - key_manager_token=await self._auth_session.key_manager_token) + id=signal_id, + users=user_ids, + session_token=await self._auth_session.session_token, + key_manager_token=await self._auth_session.key_manager_token, + ) @retry - async def list_subscribers(self, signal_id: str, skip: int = 0, limit: int = 50) -> ChannelSubscriberResponse: + async def list_subscribers( + self, signal_id: str, skip: int = 0, limit: int = 50 + ) -> ChannelSubscriberResponse: """Gets the subscribers for the specified signal. See: 'Subscribers '_ @@ -167,11 +195,16 @@ async def list_subscribers(self, signal_id: str, skip: int = 0, limit: int = 50) """ return await self._signals_api.v1_signals_id_subscribers_get( - id=signal_id, skip=skip, limit=limit, session_token=await self._auth_session.session_token, - key_manager_token=await self._auth_session.key_manager_token) - - async def list_all_subscribers(self, signal_id: str, chunk_size: int = 50, max_number: int = None) \ - -> AsyncGenerator[ChannelSubscriber, None]: + id=signal_id, + skip=skip, + limit=limit, + session_token=await self._auth_session.session_token, + key_manager_token=await self._auth_session.key_manager_token, + ) + + async def list_all_subscribers( + self, signal_id: str, chunk_size: int = 50, max_number: int = None + ) -> AsyncGenerator[ChannelSubscriber, None]: """Gets all the subscribers for the specified signal. See: 'Subscribers '_ diff --git a/symphony/bdk/core/service/stream/stream_service.py b/symphony/bdk/core/service/stream/stream_service.py index d1970f82..d0935527 100644 --- a/symphony/bdk/core/service/stream/stream_service.py +++ b/symphony/bdk/core/service/stream/stream_service.py @@ -34,8 +34,14 @@ class OboStreamService: """Class exposing OBO-enabled endpoints for stream management.""" - def __init__(self, streams_api: StreamsApi, room_membership_api: RoomMembershipApi, share_api: ShareApi, - auth_session: AuthSession, retry_config: BdkRetryConfig): + def __init__( + self, + streams_api: StreamsApi, + room_membership_api: RoomMembershipApi, + share_api: ShareApi, + auth_session: AuthSession, + retry_config: BdkRetryConfig, + ): """ :param streams_api: a generated StreamsApi instance. @@ -59,8 +65,10 @@ async def create_im(self, user_id: int) -> Stream: :param user_id: the user id to be put as room participant. :return: the created stream. """ - return await self._streams_api.v1_im_create_post(uid_list=UserIdList(value=[user_id]), - session_token=await self._auth_session.session_token) + return await self._streams_api.v1_im_create_post( + uid_list=UserIdList(value=[user_id]), + session_token=await self._auth_session.session_token, + ) @retry async def create_room(self, room_attributes: V3RoomAttributes) -> V3RoomDetail: @@ -71,8 +79,10 @@ async def create_room(self, room_attributes: V3RoomAttributes) -> V3RoomDetail: :param room_attributes: attributes of the room to be created. :return: details of created room. """ - return await self._streams_api.v3_room_create_post(payload=room_attributes, - session_token=await self._auth_session.session_token) + return await self._streams_api.v3_room_create_post( + payload=room_attributes, + session_token=await self._auth_session.session_token, + ) @retry async def get_stream(self, stream_id: str) -> V2StreamAttributes: @@ -82,8 +92,9 @@ async def get_stream(self, stream_id: str) -> V2StreamAttributes: :param stream_id: the ID of the stream to be retrieved. :return: the information about the given stream. """ - return await self._streams_api.v2_streams_sid_info_get(sid=stream_id, - session_token=await self._auth_session.session_token) + return await self._streams_api.v2_streams_sid_info_get( + sid=stream_id, session_token=await self._auth_session.session_token + ) @retry async def get_room_info(self, room_id: str) -> V3RoomDetail: @@ -93,12 +104,14 @@ async def get_room_info(self, room_id: str) -> V3RoomDetail: :param room_id: the id of the room. :return: the room details. """ - return await self._streams_api.v3_room_id_info_get(id=room_id, - session_token=await self._auth_session.session_token) + return await self._streams_api.v3_room_id_info_get( + id=room_id, session_token=await self._auth_session.session_token + ) @retry - async def list_streams(self, stream_filter: StreamFilter, skip: int = 0, - limit: int = 50) -> StreamList: + async def list_streams( + self, stream_filter: StreamFilter, skip: int = 0, limit: int = 50 + ) -> StreamList: """Returns a list of all the streams of which the requesting user is a member, sorted by creation date (ascending - oldest to newest). Wraps the `List User Streams `_ endpoint. @@ -108,12 +121,17 @@ async def list_streams(self, stream_filter: StreamFilter, skip: int = 0, :param limit: maximum number of streams to return. :return: the list of stream retrieved matching the search filter. """ - return await self._streams_api.v1_streams_list_post(filter=stream_filter, skip=skip, limit=limit, - session_token=await self._auth_session.session_token) + return await self._streams_api.v1_streams_list_post( + filter=stream_filter, + skip=skip, + limit=limit, + session_token=await self._auth_session.session_token, + ) @retry - async def list_all_streams(self, stream_filter: StreamFilter, chunk_size: int = 50, max_number: int = None) \ - -> AsyncGenerator[StreamAttributes, None]: + async def list_all_streams( + self, stream_filter: StreamFilter, chunk_size: int = 50, max_number: int = None + ) -> AsyncGenerator[StreamAttributes, None]: """Returns an asynchronous of all the streams of which the requesting user is a member, sorted by creation date (ascending - oldest to newest). Wraps the `List User Streams `_ endpoint. @@ -131,8 +149,13 @@ async def list_streams_one_page(skip, limit): return offset_based_pagination(list_streams_one_page, chunk_size, max_number) @retry - async def search_rooms(self, query: V2RoomSearchCriteria, skip: int = 0, - limit: int = 50, include_non_discoverable=False) -> V3RoomSearchResults: + async def search_rooms( + self, + query: V2RoomSearchCriteria, + skip: int = 0, + limit: int = 50, + include_non_discoverable=False, + ) -> V3RoomSearchResults: """Search for rooms according to the specified criteria. Wraps the `Search Rooms V3 `_ endpoint. @@ -142,13 +165,21 @@ async def search_rooms(self, query: V2RoomSearchCriteria, skip: int = 0, :param include_non_discoverable: set to `True` to include rooms not publicly searchable, false by default. :return: the rooms matching search criteria. """ - return await self._streams_api.v3_room_search_post(query=query, skip=skip, limit=limit, - include_non_discoverable=include_non_discoverable, - session_token=await self._auth_session.session_token) + return await self._streams_api.v3_room_search_post( + query=query, + skip=skip, + limit=limit, + include_non_discoverable=include_non_discoverable, + session_token=await self._auth_session.session_token, + ) - async def search_all_rooms(self, query: V2RoomSearchCriteria, chunk_size: int = 50, - max_number: int = None, include_non_discoverable : bool = False)\ - -> AsyncGenerator[V3RoomDetail, None]: + async def search_all_rooms( + self, + query: V2RoomSearchCriteria, + chunk_size: int = 50, + max_number: int = None, + include_non_discoverable: bool = False, + ) -> AsyncGenerator[V3RoomDetail, None]: """Search for rooms according to the specified criteria. Wraps the `Search Rooms V3 `_ endpoint. @@ -174,8 +205,11 @@ async def update_room(self, room_id: str, room_attributes: V3RoomAttributes) -> :param room_attributes: the attributes of the room to be updated. :return: the details of the updated room. """ - return await self._streams_api.v3_room_id_update_post(id=room_id, payload=room_attributes, - session_token=await self._auth_session.session_token) + return await self._streams_api.v3_room_id_update_post( + id=room_id, + payload=room_attributes, + session_token=await self._auth_session.session_token, + ) @retry async def add_member_to_room(self, user_id: int, room_id: str): @@ -187,8 +221,10 @@ async def add_member_to_room(self, user_id: int, room_id: str): :return: None """ await self._room_membership_api.v1_room_id_membership_add_post( - payload=UserId(id=user_id), id=room_id, - session_token=await self._auth_session.session_token) + payload=UserId(id=user_id), + id=room_id, + session_token=await self._auth_session.session_token, + ) @retry async def remove_member_from_room(self, user_id: int, room_id: str): @@ -200,8 +236,10 @@ async def remove_member_from_room(self, user_id: int, room_id: str): :return: None """ await self._room_membership_api.v1_room_id_membership_remove_post( - payload=UserId(id=user_id), id=room_id, - session_token=await self._auth_session.session_token) + payload=UserId(id=user_id), + id=room_id, + session_token=await self._auth_session.session_token, + ) @retry async def share(self, stream_id: str, content: ShareContent) -> V2Message: @@ -214,8 +252,11 @@ async def share(self, stream_id: str, content: ShareContent) -> V2Message: :return: the created message. """ return await self._share_api.v3_stream_sid_share_post( - sid=stream_id, share_content=content, session_token=await self._auth_session.session_token, - key_manager_token=await self._auth_session.key_manager_token) + sid=stream_id, + share_content=content, + session_token=await self._auth_session.session_token, + key_manager_token=await self._auth_session.key_manager_token, + ) @retry async def promote_user_to_room_owner(self, user_id: int, room_id: str): @@ -227,7 +268,10 @@ async def promote_user_to_room_owner(self, user_id: int, room_id: str): :return: None """ await self._room_membership_api.v1_room_id_membership_promote_owner_post( - id=room_id, payload=UserId(id=user_id), session_token=await self._auth_session.session_token) + id=room_id, + payload=UserId(id=user_id), + session_token=await self._auth_session.session_token, + ) @retry async def demote_owner_to_room_participant(self, user_id: int, room_id: str): @@ -239,12 +283,14 @@ async def demote_owner_to_room_participant(self, user_id: int, room_id: str): :return: None """ await self._room_membership_api.v1_room_id_membership_demote_owner_post( - id=room_id, payload=UserId(id=user_id), session_token=await self._auth_session.session_token) + id=room_id, + payload=UserId(id=user_id), + session_token=await self._auth_session.session_token, + ) class StreamService(OboStreamService): - """Service class to manage streams. - """ + """Service class to manage streams.""" @retry async def get_im_info(self, im_id: str) -> V1IMDetail: @@ -254,8 +300,9 @@ async def get_im_info(self, im_id: str) -> V1IMDetail: :param im_id: the id of the IM. :return: the im details. """ - return await self._streams_api.v1_im_id_info_get(id=im_id, - session_token=await self._auth_session.session_token) + return await self._streams_api.v1_im_id_info_get( + id=im_id, session_token=await self._auth_session.session_token + ) @retry async def set_room_active(self, room_id: str, active: bool) -> RoomDetail: @@ -267,8 +314,11 @@ async def set_room_active(self, room_id: str, active: bool) -> RoomDetail: :param active: the new active status (True to reactivate, false to deactivate). :return: the details of the updated room. """ - return await self._streams_api.v1_room_id_set_active_post(id=room_id, active=active, - session_token=await self._auth_session.session_token) + return await self._streams_api.v1_room_id_set_active_post( + id=room_id, + active=active, + session_token=await self._auth_session.session_token, + ) @retry async def update_im(self, im_id: str, im_attributes: V1IMAttributes) -> V1IMDetail: @@ -279,8 +329,11 @@ async def update_im(self, im_id: str, im_attributes: V1IMAttributes) -> V1IMDeta :param im_attributes: the attributes of the im to be updated. :return: the details of the updated im. """ - return await self._streams_api.v1_im_id_update_post(id=im_id, payload=im_attributes, - session_token=await self._auth_session.session_token) + return await self._streams_api.v1_im_id_update_post( + id=im_id, + payload=im_attributes, + session_token=await self._auth_session.session_token, + ) @retry async def create_im_admin(self, user_ids: [int]) -> Stream: @@ -298,8 +351,10 @@ async def create_im_admin(self, user_ids: [int]) -> Stream: :param user_ids: the list of user IDs to be put as participants. At least two user IDs must be provided. :return: the created IM or MIM. """ - return await self._streams_api.v1_admin_im_create_post(uid_list=UserIdList(value=user_ids), - session_token=await self._auth_session.session_token) + return await self._streams_api.v1_admin_im_create_post( + uid_list=UserIdList(value=user_ids), + session_token=await self._auth_session.session_token, + ) @retry async def set_room_active_admin(self, room_id: str, active: bool) -> RoomDetail: @@ -310,12 +365,15 @@ async def set_room_active_admin(self, room_id: str, active: bool) -> RoomDetail: :return: the details of the updated room. """ return await self._streams_api.v1_admin_room_id_set_active_post( - id=room_id, active=active, - session_token=await self._auth_session.session_token) + id=room_id, + active=active, + session_token=await self._auth_session.session_token, + ) @retry - async def list_streams_admin(self, stream_filter: V2AdminStreamFilter, skip: int = 0, - limit: int = 50) -> V2AdminStreamList: + async def list_streams_admin( + self, stream_filter: V2AdminStreamFilter, skip: int = 0, limit: int = 50 + ) -> V2AdminStreamList: """Retrieves all the streams across the enterprise. Wraps the `List Streams for Enterprise V2 `_ endpoint. @@ -325,11 +383,16 @@ async def list_streams_admin(self, stream_filter: V2AdminStreamFilter, skip: int :param limit: the maximum number of streams to retrieve. Must be a positive integer less or equal than 100. :return: the list of streams matching the search criteria. """ - return await self._streams_api.v2_admin_streams_list_post(filter=stream_filter, skip=skip, limit=limit, - session_token=await self._auth_session.session_token) + return await self._streams_api.v2_admin_streams_list_post( + filter=stream_filter, + skip=skip, + limit=limit, + session_token=await self._auth_session.session_token, + ) - async def list_all_streams_admin(self, stream_filter: V2AdminStreamFilter, chunk_size=50, max_number=None) \ - -> AsyncGenerator[V2AdminStreamInfo, None]: + async def list_all_streams_admin( + self, stream_filter: V2AdminStreamFilter, chunk_size=50, max_number=None + ) -> AsyncGenerator[V2AdminStreamInfo, None]: """Retrieves all the streams across the enterprise. Wraps the `List Streams for Enterprise V2 `_ endpoint. @@ -347,8 +410,13 @@ async def list_streams_admin_one_page(skip, limit): return offset_based_pagination(list_streams_admin_one_page, chunk_size, max_number) @retry - async def list_user_streams_admin(self, uid: int, stream_filter: StreamFilter = None, skip: int = 0, - limit: int = 50) -> StreamList: + async def list_user_streams_admin( + self, + uid: int, + stream_filter: StreamFilter = None, + skip: int = 0, + limit: int = 50, + ) -> StreamList: """Retrieve a list of all streams of which a user is a member. Wraps the `User Streams `_ endpoint. @@ -359,11 +427,20 @@ async def list_user_streams_admin(self, uid: int, stream_filter: StreamFilter = :return: the list of streams for the specified user. """ return await self._streams_api.v1_admin_user_uid_streams_list_post( - uid=uid, filter=stream_filter, skip=skip, limit=limit, session_token=await self._auth_session.session_token + uid=uid, + filter=stream_filter, + skip=skip, + limit=limit, + session_token=await self._auth_session.session_token, ) - async def list_all_user_streams_admin(self, uid: int, stream_filter: StreamFilter = None, chunk_size: int = 50, - max_number: int = None) -> AsyncGenerator[StreamAttributes, None]: + async def list_all_user_streams_admin( + self, + uid: int, + stream_filter: StreamFilter = None, + chunk_size: int = 50, + max_number: int = None, + ) -> AsyncGenerator[StreamAttributes, None]: """Retrieves all streams of which a user is a member, handling pagination automatically. Wraps the `User Streams `_ endpoint. @@ -373,6 +450,7 @@ async def list_all_user_streams_admin(self, uid: int, stream_filter: StreamFilte :param max_number: the total maximum number of streams to retrieve. :return: an asynchronous generator of the streams. """ + async def list_streams_one_page(skip, limit): result = await self.list_user_streams_admin(uid, stream_filter, skip, limit) return result.value if result else None @@ -380,7 +458,9 @@ async def list_streams_one_page(skip, limit): return offset_based_pagination(list_streams_one_page, chunk_size, max_number) @retry - async def list_stream_members(self, stream_id: str, skip: int = 0, limit: int = 100) -> V2MembershipList: + async def list_stream_members( + self, stream_id: str, skip: int = 0, limit: int = 100 + ) -> V2MembershipList: """List the current members of an existing stream. The stream can be of type IM, MIM, or ROOM. Wraps the `Stream Members `_ endpoint. @@ -390,11 +470,15 @@ async def list_stream_members(self, stream_id: str, skip: int = 0, limit: int = :return: the list of stream members. """ return await self._streams_api.v1_admin_stream_id_membership_list_get( - id=stream_id, skip=skip, limit=limit, - session_token=await self._auth_session.session_token) + id=stream_id, + skip=skip, + limit=limit, + session_token=await self._auth_session.session_token, + ) - async def list_all_stream_members(self, stream_id: str, chunk_size: int = 50, max_number=None) \ - -> AsyncGenerator[V2MemberInfo, None]: + async def list_all_stream_members( + self, stream_id: str, chunk_size: int = 50, max_number=None + ) -> AsyncGenerator[V2MemberInfo, None]: """List the current members of an existing stream. The stream can be of type IM, MIM, or ROOM. Wraps the `Stream Members `_ endpoint. @@ -419,5 +503,5 @@ async def list_room_members(self, room_id: str) -> MembershipList: :return: the list of room members. """ return await self._room_membership_api.v2_room_id_membership_list_get( - id=room_id, - session_token=await self._auth_session.session_token) + id=room_id, session_token=await self._auth_session.session_token + ) diff --git a/symphony/bdk/core/service/stream/stream_util.py b/symphony/bdk/core/service/stream/stream_util.py index 87ae9466..70f67ba6 100644 --- a/symphony/bdk/core/service/stream/stream_util.py +++ b/symphony/bdk/core/service/stream/stream_util.py @@ -1,6 +1,7 @@ """This module contains a set of util functions providing stream id conversion. It is possible to make conversion from the original stream id to the URLSafe encoded stream id and viceversa. """ + import base64 diff --git a/symphony/bdk/core/service/user/model/delegate_action_enum.py b/symphony/bdk/core/service/user/model/delegate_action_enum.py index c18bc254..127c1ac2 100644 --- a/symphony/bdk/core/service/user/model/delegate_action_enum.py +++ b/symphony/bdk/core/service/user/model/delegate_action_enum.py @@ -5,5 +5,6 @@ class DelegateActionEnum(enum.Enum): """ Enumeration of the action that can be performed with a delegate. """ + ADD = "ADD" REMOVE = "REMOVE" diff --git a/symphony/bdk/core/service/user/model/role_id.py b/symphony/bdk/core/service/user/model/role_id.py index 5fe25e7f..5e9b1fff 100644 --- a/symphony/bdk/core/service/user/model/role_id.py +++ b/symphony/bdk/core/service/user/model/role_id.py @@ -5,6 +5,7 @@ class RoleId(enum.Enum): """Static roles that have special logic attached to them. See: `Symphony Roles `_ """ + ADMINISTRATOR = "ADMINISTRATOR" SUPER_ADMINISTRATOR = "SUPER_ADMINISTRATOR" COMPLIANCE_OFFICER = "COMPLIANCE_OFFICER" diff --git a/symphony/bdk/core/service/user/user_service.py b/symphony/bdk/core/service/user/user_service.py index 79e12c90..2f45aed3 100644 --- a/symphony/bdk/core/service/user/user_service.py +++ b/symphony/bdk/core/service/user/user_service.py @@ -1,11 +1,12 @@ import base64 import json from pathlib import Path -from typing import Union, AsyncGenerator +from typing import AsyncGenerator, Union from symphony.bdk.core.auth.auth_session import AuthSession from symphony.bdk.core.config.model.bdk_retry_config import BdkRetryConfig -from symphony.bdk.core.service.pagination import offset_based_pagination, cursor_based_pagination +from symphony.bdk.core.retry import retry +from symphony.bdk.core.service.pagination import cursor_based_pagination, offset_based_pagination from symphony.bdk.core.service.user.model.delegate_action_enum import DelegateActionEnum from symphony.bdk.core.service.user.model.role_id import RoleId from symphony.bdk.gen.agent_api.audit_trail_api import AuditTrailApi @@ -30,14 +31,12 @@ from symphony.bdk.gen.pod_model.user_search_query import UserSearchQuery from symphony.bdk.gen.pod_model.user_search_results import UserSearchResults from symphony.bdk.gen.pod_model.user_status import UserStatus +from symphony.bdk.gen.pod_model.user_suspension import UserSuspension from symphony.bdk.gen.pod_model.user_v2 import UserV2 from symphony.bdk.gen.pod_model.v2_user_attributes import V2UserAttributes from symphony.bdk.gen.pod_model.v2_user_create import V2UserCreate from symphony.bdk.gen.pod_model.v2_user_detail import V2UserDetail from symphony.bdk.gen.pod_model.v2_user_list import V2UserList -from symphony.bdk.gen.pod_model.user_suspension import UserSuspension - -from symphony.bdk.core.retry import retry class OboUserService: @@ -50,10 +49,13 @@ class OboUserService: * follow/unfollow users """ - def __init__(self, user_api: UserApi, - users_api: UsersApi, - auth_session: AuthSession, - retry_config: BdkRetryConfig): + def __init__( + self, + user_api: UserApi, + users_api: UsersApi, + auth_session: AuthSession, + retry_config: BdkRetryConfig, + ): self._user_api = user_api self._users_api = users_api self._auth_session = auth_session @@ -61,10 +63,7 @@ def __init__(self, user_api: UserApi, @retry async def list_users_by_ids( - self, - user_ids: [int], - local: bool = False, - active: bool = None + self, user_ids: [int], local: bool = False, active: bool = None ) -> V2UserList: """Search users by user ids. See : `Users Lookup v3 `_ @@ -79,22 +78,19 @@ async def list_users_by_ids( :return: Users found by user ids. """ - user_ids_str = ','.join(map(str, user_ids)) + user_ids_str = ",".join(map(str, user_ids)) params = { - 'uid': user_ids_str, - 'local': local, - 'session_token': await self._auth_session.session_token + "uid": user_ids_str, + "local": local, + "session_token": await self._auth_session.session_token, } if active is not None: - params['active'] = active + params["active"] = active return await self._users_api.v3_users_get(**params) @retry async def list_users_by_emails( - self, - emails: [str], - local: bool = False, - active: bool = None + self, emails: [str], local: bool = False, active: bool = None ) -> V2UserList: """Search users by emails. See : `Users Lookup v3 `_ @@ -109,22 +105,18 @@ async def list_users_by_emails( :return: Users found by emails. """ - emails_str = ','.join(emails) + emails_str = ",".join(emails) params = { - 'email': emails_str, - 'local': local, - 'session_token': await self._auth_session.session_token + "email": emails_str, + "local": local, + "session_token": await self._auth_session.session_token, } if active is not None: - params['active'] = active + params["active"] = active return await self._users_api.v3_users_get(**params) @retry - async def list_users_by_usernames( - self, - usernames: [str], - active: bool = None - ) -> V2UserList: + async def list_users_by_usernames(self, usernames: [str], active: bool = None) -> V2UserList: """Search users by usernames. See : `Users Lookup v3 `_ @@ -135,23 +127,19 @@ async def list_users_by_usernames( :return: Users found by usernames. """ - usernames_str = ','.join(usernames) + usernames_str = ",".join(usernames) params = { - 'username': usernames_str, - 'local': True, - 'session_token': await self._auth_session.session_token + "username": usernames_str, + "local": True, + "session_token": await self._auth_session.session_token, } if active is not None: - params['active'] = active + params["active"] = active return await self._users_api.v3_users_get(**params) @retry async def search_users( - self, - query: UserSearchQuery, - local: bool = False, - skip: int = 0, - limit: int = 50 + self, query: UserSearchQuery, local: bool = False, skip: int = 0, limit: int = 50 ) -> UserSearchResults: """Search for users by first name, last name, display name, and email; optionally, filter results by company, title, location, marketCoverage, responsibility, function, or instrument. @@ -168,20 +156,20 @@ async def search_users( """ params = { - 'search_request': query, - 'local': local, - 'session_token': await self._auth_session.session_token, - 'skip': skip, - 'limit': limit + "search_request": query, + "local": local, + "session_token": await self._auth_session.session_token, + "skip": skip, + "limit": limit, } return await self._users_api.v1_user_search_post(**params) async def search_all_users( - self, - query: UserSearchQuery, - local: bool = False, - chunk_size: int = 50, - max_number: int = None + self, + query: UserSearchQuery, + local: bool = False, + chunk_size: int = 50, + max_number: int = None, ) -> AsyncGenerator[UserV2, None]: """Search for users by first name, last name, display name, and email; optionally, filter results by company, title, location, marketCoverage, responsibility, function, or instrument. @@ -206,11 +194,7 @@ async def search_users_one_page(skip: int, limit: int): return offset_based_pagination(search_users_one_page, chunk_size, max_number) @retry - async def follow_user( - self, - follower_ids: [int], - user_id: int - ) -> None: + async def follow_user(self, follower_ids: [int], user_id: int) -> None: """Make a list of users to start following a specific user. See: `Follow User `_ @@ -218,18 +202,14 @@ async def follow_user( :param user_id: The id of the user to be followed. """ params = { - 'uid': user_id, - 'uid_list': FollowersList(followers=UserIdList(value=follower_ids)), - 'session_token': await self._auth_session.session_token + "uid": user_id, + "uid_list": FollowersList(followers=UserIdList(value=follower_ids)), + "session_token": await self._auth_session.session_token, } await self._user_api.v1_user_uid_follow_post(**params) @retry - async def unfollow_user( - self, - follower_ids: [int], - user_id: int - ) -> None: + async def unfollow_user(self, follower_ids: [int], user_id: int) -> None: """Make a list of users to stop following a specific user. See: `Unfollow User `_ @@ -237,9 +217,9 @@ async def unfollow_user( :param user_id: The id of the user to be unfollowed. """ params = { - 'uid': user_id, - 'uid_list': FollowersList(followers=UserIdList(value=follower_ids)), - 'session_token': await self._auth_session.session_token + "uid": user_id, + "uid_list": FollowersList(followers=UserIdList(value=follower_ids)), + "session_token": await self._auth_session.session_token, } await self._user_api.v1_user_uid_unfollow_post(**params) @@ -258,41 +238,34 @@ class UserService(OboUserService): * Get, update status of a user """ - def __init__(self, user_api: UserApi, - users_api: UsersApi, - audit_trail_api: AuditTrailApi, - system_api: SystemApi, - auth_session: AuthSession, - retry_config: BdkRetryConfig, - manifest: str): + def __init__( + self, + user_api: UserApi, + users_api: UsersApi, + audit_trail_api: AuditTrailApi, + system_api: SystemApi, + auth_session: AuthSession, + retry_config: BdkRetryConfig, + manifest: str, + ): super().__init__(user_api, users_api, auth_session, retry_config) self._audit_trail_api = audit_trail_api self._system_api = system_api self._manifest = manifest @retry - async def get_user_detail( - self, - user_id: int - ) -> V2UserDetail: + async def get_user_detail(self, user_id: int) -> V2UserDetail: """Retrieve user details of a particular user. See: 'Get User v2 '_ :param user_id: User Id :return: Details of the user. """ - params = { - 'uid': user_id, - 'session_token': await self._auth_session.session_token - } + params = {"uid": user_id, "session_token": await self._auth_session.session_token} return await self._user_api.v2_admin_user_uid_get(**params) @retry - async def list_user_details( - self, - skip: int = 0, - limit: int = 50 - ) -> [V2UserDetail]: + async def list_user_details(self, skip: int = 0, limit: int = 50) -> [V2UserDetail]: """Retrieve all users in the company (pod). See: 'List Users V2 '_ @@ -301,17 +274,15 @@ async def list_user_details( :return: List of details of all users in the company. """ params = { - 'session_token': await self._auth_session.session_token, - 'skip': skip, - 'limit': limit + "session_token": await self._auth_session.session_token, + "skip": skip, + "limit": limit, } user_detail_list = await self._user_api.v2_admin_user_list_get(**params) return user_detail_list.value async def list_all_user_details( - self, - chunk_size: int = 50, - max_number: int = None + self, chunk_size: int = 50, max_number: int = None ) -> AsyncGenerator[V2UserDetail, None]: """Retrieve all users in the company (pod). Same as :func:`~list_user_details` but returns an asynchronous generator which performs the paginated calls with @@ -327,10 +298,7 @@ async def list_all_user_details( @retry async def list_user_details_by_filter( - self, - user_filter: UserFilter, - skip: int = 0, - limit: int = 50 + self, user_filter: UserFilter, skip: int = 0, limit: int = 50 ) -> [V2UserDetail]: """Retrieve a list of users in the company (pod) by a filter. See: `Find Users V1 `_ @@ -341,19 +309,16 @@ async def list_user_details_by_filter( :return: List of retrieved users. """ params = { - 'payload': user_filter, - 'session_token': await self._auth_session.session_token, - 'skip': skip, - 'limit': limit + "payload": user_filter, + "session_token": await self._auth_session.session_token, + "skip": skip, + "limit": limit, } user_detail_list = await self._user_api.v1_admin_user_find_post(**params) return user_detail_list.value async def list_all_user_details_by_filter( - self, - user_filter: UserFilter, - chunk_size: int = 50, - max_number: int = None + self, user_filter: UserFilter, chunk_size: int = 50, max_number: int = None ) -> AsyncGenerator[V2UserDetail, None]: """Retrieve an asynchronous generator of users in the company (pod) by a filter. Same as :func:`~list_user_details_by_filter` but returns an generator which performs the paginated @@ -373,11 +338,7 @@ async def list_user_details_one_page(skip, limit): return offset_based_pagination(list_user_details_one_page, chunk_size, max_number) @retry - async def add_role( - self, - user_id: int, - role_id: RoleId - ) -> None: + async def add_role(self, user_id: int, role_id: RoleId) -> None: """Add a role to a user. See: `Add Role `_ @@ -385,9 +346,9 @@ async def add_role( :param role_id: role id """ params = { - 'uid': user_id, - 'payload': StringId(id=role_id.value), - 'session_token': await self._auth_session.session_token + "uid": user_id, + "payload": StringId(id=role_id.value), + "session_token": await self._auth_session.session_token, } await self._user_api.v1_admin_user_uid_roles_add_post(**params) @@ -398,18 +359,12 @@ async def list_roles(self) -> [RoleDetail]: :return: List of all roles details in the pod. """ - params = { - 'session_token': await self._auth_session.session_token - } + params = {"session_token": await self._auth_session.session_token} role_list = await self._system_api.v1_admin_system_roles_list_get(**params) return role_list.value @retry - async def remove_role( - self, - user_id: int, - role_id: RoleId - ) -> None: + async def remove_role(self, user_id: int, role_id: RoleId) -> None: """Remove a role from a user. See: `Remove Role `_ @@ -417,36 +372,26 @@ async def remove_role( :param role_id: role id """ params = { - 'uid': user_id, - 'payload': StringId(id=role_id.value), - 'session_token': await self._auth_session.session_token + "uid": user_id, + "payload": StringId(id=role_id.value), + "session_token": await self._auth_session.session_token, } await self._user_api.v1_admin_user_uid_roles_remove_post(**params) @retry - async def get_avatar( - self, - user_id: int - ) -> [Avatar]: + async def get_avatar(self, user_id: int) -> [Avatar]: """Get the url of avatar of a user. See: `User Avatar `_ :param user_id: User id :return: List of avatar urls of the user """ - params = { - 'uid': user_id, - 'session_token': await self._auth_session.session_token - } + params = {"uid": user_id, "session_token": await self._auth_session.session_token} avatar_list = await self._user_api.v1_admin_user_uid_avatar_get(**params) return avatar_list.value @retry - async def update_avatar( - self, - user_id: int, - image: Union[str, bytes] - ) -> None: + async def update_avatar(self, user_id: int, image: Union[str, bytes]) -> None: """Update avatar of a user. See: `Update User Avatar `_ @@ -457,51 +402,35 @@ async def update_avatar( if isinstance(image, bytes): image = str(base64.standard_b64encode(image)) params = { - 'uid': user_id, - 'payload': AvatarUpdate(image=image), - 'session_token': await self._auth_session.session_token + "uid": user_id, + "payload": AvatarUpdate(image=image), + "session_token": await self._auth_session.session_token, } await self._user_api.v1_admin_user_uid_avatar_update_post(**params) @retry - async def get_disclaimer( - self, - user_id: int - ) -> Disclaimer: + async def get_disclaimer(self, user_id: int) -> Disclaimer: """Get disclaimer assigned to a user. See: `User Disclaimer `_ :param user_id: user id :return: Disclaimer assigned to the user. """ - params = { - 'uid': user_id, - 'session_token': await self._auth_session.session_token - } + params = {"uid": user_id, "session_token": await self._auth_session.session_token} return await self._user_api.v1_admin_user_uid_disclaimer_get(**params) @retry - async def remove_disclaimer( - self, - user_id: int - ) -> None: + async def remove_disclaimer(self, user_id: int) -> None: """Unassign disclaimer from a user. See: `Unassign User Disclaimer `_ :param user_id: user id """ - params = { - 'uid': user_id, - 'session_token': await self._auth_session.session_token - } + params = {"uid": user_id, "session_token": await self._auth_session.session_token} await self._user_api.v1_admin_user_uid_disclaimer_delete(**params) @retry - async def add_disclaimer( - self, - user_id: int, - disclaimer_id: str - ) -> None: + async def add_disclaimer(self, user_id: int, disclaimer_id: str) -> None: """Assign disclaimer to a user. See: `Update User Disclaimer `_ @@ -509,36 +438,27 @@ async def add_disclaimer( :param disclaimer_id: Disclaimer to be assigned """ params = { - 'uid': user_id, - 'payload': StringId(id=disclaimer_id), - 'session_token': await self._auth_session.session_token + "uid": user_id, + "payload": StringId(id=disclaimer_id), + "session_token": await self._auth_session.session_token, } await self._user_api.v1_admin_user_uid_disclaimer_update_post(**params) @retry - async def get_delegates( - self, - user_id: int - ) -> [int]: + async def get_delegates(self, user_id: int) -> [int]: """Get delegates assigned to a user. See: `User Delegates `_ :param user_id: User id. :return: List of delegates assigned to a user. """ - params = { - 'uid': user_id, - 'session_token': await self._auth_session.session_token - } + params = {"uid": user_id, "session_token": await self._auth_session.session_token} delegates_list = await self._user_api.v1_admin_user_uid_delegates_get(**params) return delegates_list.value @retry async def update_delegates( - self, - user_id: int, - delegate_user_id: int, - action: DelegateActionEnum + self, user_id: int, delegate_user_id: int, action: DelegateActionEnum ) -> None: """Update delegates assigned to a user. See: `Update User Delegates `_ @@ -548,36 +468,26 @@ async def update_delegates( :param action: Action to be performed """ params = { - 'uid': user_id, - 'payload': DelegateAction(user_id=delegate_user_id, action=action.value), - 'session_token': await self._auth_session.session_token + "uid": user_id, + "payload": DelegateAction(user_id=delegate_user_id, action=action.value), + "session_token": await self._auth_session.session_token, } await self._user_api.v1_admin_user_uid_delegates_update_post(**params) @retry - async def get_feature_entitlements( - self, - user_id: int - ) -> [Feature]: + async def get_feature_entitlements(self, user_id: int) -> [Feature]: """Get feature entitlements of a user. See: `User Features `_ :param user_id: User id. :return: List of feature entitlements of the user. """ - params = { - 'uid': user_id, - 'session_token': await self._auth_session.session_token - } + params = {"uid": user_id, "session_token": await self._auth_session.session_token} feature_list = await self._user_api.v1_admin_user_uid_features_get(**params) return feature_list.value @retry - async def update_feature_entitlements( - self, - user_id: int, - features: [Feature] - ) -> None: + async def update_feature_entitlements(self, user_id: int, features: [Feature]) -> None: """Update feature entitlements of a user. See: `Update User Features `_ @@ -585,35 +495,25 @@ async def update_feature_entitlements( :param features: List of feature entitlements to be updated """ params = { - 'uid': user_id, - 'payload': FeatureList(value=features), - 'session_token': await self._auth_session.session_token + "uid": user_id, + "payload": FeatureList(value=features), + "session_token": await self._auth_session.session_token, } await self._user_api.v1_admin_user_uid_features_update_post(**params) @retry - async def get_status( - self, - user_id: int - ) -> UserStatus: + async def get_status(self, user_id: int) -> UserStatus: """Get status of a user. See: `User Status `_ :param user_id: User id. :return: Status of the user. """ - params = { - 'uid': user_id, - 'session_token': await self._auth_session.session_token - } + params = {"uid": user_id, "session_token": await self._auth_session.session_token} return await self._user_api.v1_admin_user_uid_status_get(**params) @retry - async def update_status( - self, - user_id: int, - user_status: UserStatus - ) -> None: + async def update_status(self, user_id: int, user_status: UserStatus) -> None: """Update the status of a user. See: `Update User Status `_ @@ -621,19 +521,15 @@ async def update_status( :param user_status: Status to be updated to the user. """ params = { - 'uid': user_id, - 'payload': user_status, - 'session_token': await self._auth_session.session_token + "uid": user_id, + "payload": user_status, + "session_token": await self._auth_session.session_token, } await self._user_api.v1_admin_user_uid_status_update_post(**params) @retry async def list_user_followers( - self, - user_id: int, - limit: int = 100, - before: str = None, - after: str = None + self, user_id: int, limit: int = 100, before: str = None, after: str = None ) -> FollowersListResponse: """Returns the list of followers of a specific user. See: `List User Followers `_ @@ -645,21 +541,18 @@ async def list_user_followers( :return: List of followers of a specific user. """ params = { - 'uid': user_id, - 'limit': limit, - 'session_token': await self._auth_session.session_token + "uid": user_id, + "limit": limit, + "session_token": await self._auth_session.session_token, } if before is not None: - params['before'] = before + params["before"] = before if after is not None: - params['after'] = after + params["after"] = after return await self._user_api.v1_user_uid_followers_get(**params) async def list_all_user_followers( - self, - user_id: int, - chunk_size: int = 100, - max_number: int = None + self, user_id: int, chunk_size: int = 100, max_number: int = None ) -> AsyncGenerator[int, None]: """Returns an asynchronous generator of the IDs of users who are followers of a specific user. See: `List User Followers `_ @@ -673,17 +566,13 @@ async def list_all_user_followers( async def user_followers_one_page(limit, after=None): result = await self.list_user_followers(user_id, limit, after=after) - return result.followers, getattr(result.pagination.cursors, 'after', None) + return result.followers, getattr(result.pagination.cursors, "after", None) return cursor_based_pagination(user_followers_one_page, chunk_size, max_number) @retry async def list_users_following( - self, - user_id: int, - limit: int = 100, - before: str = None, - after: str = None + self, user_id: int, limit: int = 100, before: str = None, after: str = None ) -> FollowingListResponse: """Returns the list of users followed by a specific user. See: `List Users Followed `_ @@ -695,21 +584,18 @@ async def list_users_following( :return: The list of users followed by a specific user. """ params = { - 'uid': user_id, - 'limit': limit, - 'session_token': await self._auth_session.session_token + "uid": user_id, + "limit": limit, + "session_token": await self._auth_session.session_token, } if before is not None: - params['before'] = before + params["before"] = before if after is not None: - params['after'] = after + params["after"] = after return await self._user_api.v1_user_uid_following_get(**params) async def list_all_users_following( - self, - user_id: int, - chunk_size: int = 100, - max_number: int = None + self, user_id: int, chunk_size: int = 100, max_number: int = None ) -> AsyncGenerator[int, None]: """Returns n asynchronous generator of the IDs of users followed by a given user. See: `List Users Followed `_ @@ -723,33 +609,23 @@ async def list_all_users_following( async def user_following_one_page(limit, after=None): result = await self.list_users_following(user_id, limit, after=after) - return result.following, getattr(result.pagination.cursors, 'after', None) + return result.following, getattr(result.pagination.cursors, "after", None) return cursor_based_pagination(user_following_one_page, chunk_size, max_number) @retry - async def create( - self, - payload: V2UserCreate - ) -> V2UserDetail: + async def create(self, payload: V2UserCreate) -> V2UserDetail: """Create a new user. See: `Create User v2 `_ :param payload: User's details to create. :return: Created user details. """ - params = { - 'payload': payload, - 'session_token': await self._auth_session.session_token - } + params = {"payload": payload, "session_token": await self._auth_session.session_token} return await self._user_api.v2_admin_user_create_post(**params) @retry - async def update( - self, - user_id: int, - payload: V2UserAttributes - ) -> V2UserDetail: + async def update(self, user_id: int, payload: V2UserAttributes) -> V2UserDetail: """Updates an existing user. See: `Update User v2 `_ @@ -758,22 +634,22 @@ async def update( :return: User with the updated user details. """ params = { - 'uid': user_id, - 'payload': payload, - 'session_token': await self._auth_session.session_token + "uid": user_id, + "payload": payload, + "session_token": await self._auth_session.session_token, } return await self._user_api.v2_admin_user_uid_update_post(**params) @retry async def list_audit_trail( - self, - start_timestamp: int, - end_timestamp: int = None, - initiator_id: int = None, - role: RoleId = None, - limit: int = 50, - before: int = None, - after: int = None + self, + start_timestamp: int, + end_timestamp: int = None, + initiator_id: int = None, + role: RoleId = None, + limit: int = 50, + before: int = None, + after: int = None, ) -> V1AuditTrailInitiatorList: """Returns audit trail of actions performed by a privileged user in a given period of time. See: `List Audit Trail v1 `_ @@ -788,31 +664,31 @@ async def list_audit_trail( :return: List of audit trail initiator. """ params = { - 'start_timestamp': start_timestamp, - 'limit': limit, - 'session_token': await self._auth_session.session_token, - 'key_manager_token': await self._auth_session.key_manager_token + "start_timestamp": start_timestamp, + "limit": limit, + "session_token": await self._auth_session.session_token, + "key_manager_token": await self._auth_session.key_manager_token, } if end_timestamp is not None: - params['end_timestamp'] = end_timestamp + params["end_timestamp"] = end_timestamp if initiator_id is not None: - params['initiator_id'] = initiator_id + params["initiator_id"] = initiator_id if role is not None: - params['role'] = role.value + params["role"] = role.value if before is not None: - params['before'] = before + params["before"] = before if after is not None: - params['after'] = after + params["after"] = after return await self._audit_trail_api.v1_audittrail_privilegeduser_get(**params) async def list_all_audit_trail( - self, - start_timestamp: int, - end_timestamp: int = None, - initiator_id: int = None, - role: RoleId = None, - chunk_size: int = 100, - max_number: int = None + self, + start_timestamp: int, + end_timestamp: int = None, + initiator_id: int = None, + role: RoleId = None, + chunk_size: int = 100, + max_number: int = None, ) -> AsyncGenerator[V1AuditTrailInitiatorList, None]: """Returns an asynchronous generation of audit trail of actions performed by a privileged user in a given period of time. @@ -829,22 +705,20 @@ async def list_all_audit_trail( """ async def audit_trail_one_page(limit, after=None): - result = await self.list_audit_trail(start_timestamp=start_timestamp, - end_timestamp=end_timestamp, - initiator_id=initiator_id, - role=role, - limit=limit, - after=after) - return result.items, getattr(result.pagination.cursors, 'after', None) + result = await self.list_audit_trail( + start_timestamp=start_timestamp, + end_timestamp=end_timestamp, + initiator_id=initiator_id, + role=role, + limit=limit, + after=after, + ) + return result.items, getattr(result.pagination.cursors, "after", None) return cursor_based_pagination(audit_trail_one_page, chunk_size, max_number) @retry - async def suspend_user( - self, - user_id: int, - user_suspension: UserSuspension - ) -> None: + async def suspend_user(self, user_id: int, user_suspension: UserSuspension) -> None: """Suspends or re-activates (unsuspend) a user account. See: `Suspend User Account v1 `_ @@ -852,20 +726,15 @@ async def suspend_user( :param user_suspension: User suspension payload. """ params = { - 'user_id': user_id, - 'payload': user_suspension, - 'session_token': await self._auth_session.session_token + "user_id": user_id, + "payload": user_suspension, + "session_token": await self._auth_session.session_token, } await self._user_api.v1_admin_user_user_id_suspension_update_put(**params) @retry - async def suspend( - self, - user_id: int, - reason: str = None, - until: int = None - ) -> None: + async def suspend(self, user_id: int, reason: str = None, until: int = None) -> None: """Suspends a user account. Calling this endpoint requires a service account with the User Provisioning role. See: `Suspend User Account v1 `_ @@ -875,20 +744,19 @@ async def suspend( :param until: Time till when the user should be suspended in millis """ - user_suspension = UserSuspension(suspended=True, suspended_until=until, suspension_reason=reason) + user_suspension = UserSuspension( + suspended=True, suspended_until=until, suspension_reason=reason + ) params = { - 'user_id': user_id, - 'payload': user_suspension, - 'session_token': await self._auth_session.session_token + "user_id": user_id, + "payload": user_suspension, + "session_token": await self._auth_session.session_token, } await self._user_api.v1_admin_user_user_id_suspension_update_put(**params) @retry - async def unsuspend( - self, - user_id: int - ) -> None: + async def unsuspend(self, user_id: int) -> None: """Unsuspend (Re-activates) a user account. Calling this endpoint requires a service account with the User Provisioning role. See: `Suspend User Account v1 `_ @@ -897,9 +765,9 @@ async def unsuspend( """ params = { - 'user_id': user_id, - 'payload': UserSuspension(suspended=False), - 'session_token': await self._auth_session.session_token + "user_id": user_id, + "payload": UserSuspension(suspended=False), + "session_token": await self._auth_session.session_token, } await self._user_api.v1_admin_user_user_id_suspension_update_put(**params) @@ -914,7 +782,7 @@ async def update_manifest_from_json(self, manifest_data: str) -> None: """ await self._user_api.v1_user_manifest_own_post( session_token=await self._auth_session.session_token, - manifest=ServiceAccountManifest(manifest_data) + manifest=ServiceAccountManifest(manifest_data), ) async def update_manifest_from_file(self) -> None: @@ -927,16 +795,18 @@ async def update_manifest_from_file(self) -> None: """ file_path = Path(self._manifest) - with file_path.open('r', encoding='utf-8') as f: + with file_path.open("r", encoding="utf-8") as f: content = json.load(f) manifest_text = json.dumps(content) await self._user_api.v1_user_manifest_own_post( session_token=await self._auth_session.session_token, - manifest=ServiceAccountManifest(manifest_text) + manifest=ServiceAccountManifest(manifest_text), ) @retry async def get_manifest(self) -> ServiceAccountManifest: """Retrieves own user manifest""" - return await self._user_api.v1_user_manifest_own_get(session_token=await self._auth_session.session_token) + return await self._user_api.v1_user_manifest_own_get( + session_token=await self._auth_session.session_token + ) diff --git a/symphony/bdk/core/service/user/user_util.py b/symphony/bdk/core/service/user/user_util.py index ab99994f..d21bf782 100644 --- a/symphony/bdk/core/service/user/user_util.py +++ b/symphony/bdk/core/service/user/user_util.py @@ -4,6 +4,7 @@ positive value) which allows for 134 million pods. This leaves 36 lowest bits for the user ID, which allows 68.7 billion users per tenant. """ + TENANT_ID_BIT_LENGTH = 27 SUBTENANT_ID_BIT_LENGTH = 36 TENANT_ID_INDEX = 1 @@ -20,8 +21,7 @@ def extract_tenant_id(user_id: int): class NumberUtil: - """Used to compute the segments bases in the input sizes - """ + """Used to compute the segments bases in the input sizes""" def __init__(self, sizes: []): self._segments = [] @@ -38,15 +38,13 @@ def __init__(self, sizes: []): raise ValueError("total size is larger than the bit-count of 64") def extract(self, value: int, index: int): - """Extract the tenant_id given the user_id and the index - """ + """Extract the tenant_id given the user_id and the index""" segment = self._segments[index] return value >> segment._shift & segment._mask class Segment: - """Helper class to initialize a Segment - """ + """Helper class to initialize a Segment""" def __init__(self, size: int, shift: int): mask = 0 diff --git a/symphony/bdk/core/service/version/agent_version_service.py b/symphony/bdk/core/service/version/agent_version_service.py index 40b774f5..2d1078a9 100644 --- a/symphony/bdk/core/service/version/agent_version_service.py +++ b/symphony/bdk/core/service/version/agent_version_service.py @@ -1,7 +1,6 @@ import re from datetime import datetime, timezone -from symphony.bdk.core.auth.auth_session import AuthSession from symphony.bdk.core.auth.jwt_helper import generate_expiration_time from symphony.bdk.core.config.model.bdk_retry_config import BdkRetryConfig from symphony.bdk.core.retry import retry @@ -14,9 +13,7 @@ class AgentVersionService: - """Service class has one purpose only. It checks if version of agents supports simplified key delivery mechanism - - """ + """Service class has one purpose only. It checks if version of agents supports simplified key delivery mechanism""" def __init__(self, signals_api: SignalsApi, retry_config: BdkRetryConfig): self._signals_api = signals_api @@ -25,7 +22,7 @@ def __init__(self, signals_api: SignalsApi, retry_config: BdkRetryConfig): self._expire_at = -1 async def is_skd_supported(self) -> bool: - """ AgentVersionService stores cached version flag. + """AgentVersionService stores cached version flag. Caching interval is the same as in to session token caching. Once cache is expired it calls agent info api to update version. @@ -33,15 +30,13 @@ async def is_skd_supported(self) -> bool: """ if ( self._is_skd_supported is not None - and self._expire_at - > datetime.now(timezone.utc).timestamp() + and self._expire_at > datetime.now(timezone.utc).timestamp() ): return self._is_skd_supported self._expire_at = generate_expiration_time() self._is_skd_supported = await self._get_agent_skd_support() return self._is_skd_supported - @retry async def _get_agent_skd_support(self) -> bool: try: diff --git a/symphony/bdk/core/service_factory.py b/symphony/bdk/core/service_factory.py index 8eaf9df1..6db14197 100644 --- a/symphony/bdk/core/service_factory.py +++ b/symphony/bdk/core/service_factory.py @@ -1,23 +1,31 @@ """Service factory module""" + from symphony.bdk.core.auth.auth_session import AuthSession from symphony.bdk.core.client.api_client_factory import ApiClientFactory from symphony.bdk.core.config.model.bdk_config import BdkConfig from symphony.bdk.core.service.application.application_service import ApplicationService -from symphony.bdk.core.service.connection.connection_service import ConnectionService, OboConnectionService -from symphony.bdk.core.service.datafeed.abstract_datafeed_loop import DatafeedVersion, AbstractDatafeedLoop -from symphony.bdk.core.service.datafeed.abstract_ackId_event_loop import AbstractAckIdEventLoop +from symphony.bdk.core.service.connection.connection_service import ( + ConnectionService, + OboConnectionService, +) +from symphony.bdk.core.service.datafeed.abstract_datafeed_loop import ( + AbstractDatafeedLoop, + DatafeedVersion, +) from symphony.bdk.core.service.datafeed.abstract_datahose_loop import AbstractDatahoseLoop from symphony.bdk.core.service.datafeed.datafeed_loop_v1 import DatafeedLoopV1 from symphony.bdk.core.service.datafeed.datafeed_loop_v2 import DatafeedLoopV2 from symphony.bdk.core.service.datafeed.datahose_loop import DatahoseLoop from symphony.bdk.core.service.health.health_service import HealthService from symphony.bdk.core.service.message.message_service import MessageService, OboMessageService -from symphony.bdk.core.service.message.multi_attachments_messages_api import MultiAttachmentsMessagesApi -from symphony.bdk.core.service.presence.presence_service import PresenceService, OboPresenceService +from symphony.bdk.core.service.message.multi_attachments_messages_api import ( + MultiAttachmentsMessagesApi, +) +from symphony.bdk.core.service.presence.presence_service import OboPresenceService, PresenceService from symphony.bdk.core.service.session.session_service import SessionService -from symphony.bdk.core.service.signal.signal_service import SignalService, OboSignalService -from symphony.bdk.core.service.stream.stream_service import StreamService, OboStreamService -from symphony.bdk.core.service.user.user_service import UserService, OboUserService +from symphony.bdk.core.service.signal.signal_service import OboSignalService, SignalService +from symphony.bdk.core.service.stream.stream_service import OboStreamService, StreamService +from symphony.bdk.core.service.user.user_service import OboUserService, UserService from symphony.bdk.core.service.version.agent_version_service import AgentVersionService from symphony.bdk.gen.agent_api.attachments_api import AttachmentsApi from symphony.bdk.gen.agent_api.audit_trail_api import AuditTrailApi @@ -53,16 +61,15 @@ class ServiceFactory: """ def __init__( - self, - api_client_factory: ApiClientFactory, - auth_session: AuthSession, - config: BdkConfig + self, api_client_factory: ApiClientFactory, auth_session: AuthSession, config: BdkConfig ): self._pod_client = api_client_factory.get_pod_client() self._agent_client = api_client_factory.get_agent_client() self._auth_session = auth_session self._config = config - self._session_service = SessionService(SessionApi(self._pod_client), self._auth_session, self._config.retry) + self._session_service = SessionService( + SessionApi(self._pod_client), self._auth_session, self._config.retry + ) def get_user_service(self) -> UserService: """Returns a fully initialized UserService @@ -76,7 +83,7 @@ def get_user_service(self) -> UserService: PodSystemApi(self._pod_client), self._auth_session, self._config.retry, - self._config.manifest + self._config.manifest, ) def get_message_service(self) -> MessageService: @@ -93,7 +100,7 @@ def get_message_service(self) -> MessageService: AttachmentsApi(self._agent_client), DefaultApi(self._pod_client), self._auth_session, - self._config.retry + self._config.retry, ) def get_connection_service(self) -> ConnectionService: @@ -102,9 +109,7 @@ def get_connection_service(self) -> ConnectionService: :return: a new ConnectionService instance. """ return ConnectionService( - ConnectionApi(self._pod_client), - self._auth_session, - self._config.retry + ConnectionApi(self._pod_client), self._auth_session, self._config.retry ) def get_stream_service(self) -> StreamService: @@ -117,7 +122,8 @@ def get_stream_service(self) -> StreamService: RoomMembershipApi(self._pod_client), ShareApi(self._agent_client), self._auth_session, - self._config.retry) + self._config.retry, + ) def get_application_service(self) -> ApplicationService: """Returns a fully initialized ApplicationService @@ -128,7 +134,7 @@ def get_application_service(self) -> ApplicationService: ApplicationApi(self._pod_client), AppEntitlementApi(self._pod_client), self._auth_session, - self._config.retry + self._config.retry, ) def get_signal_service(self) -> SignalService: @@ -136,11 +142,7 @@ def get_signal_service(self) -> SignalService: :return: a new SignalService instance """ - return SignalService( - SignalsApi(self._agent_client), - self._auth_session, - self._config.retry - ) + return SignalService(SignalsApi(self._agent_client), self._auth_session, self._config.retry) def get_session_service(self) -> SessionService: """Returns a fully initialized SessionService @@ -160,13 +162,10 @@ def get_datafeed_loop(self) -> AbstractDatafeedLoop: DatafeedApi(self._agent_client), self._session_service, self._auth_session, - self._config + self._config, ) return DatafeedLoopV2( - DatafeedApi(self._agent_client), - self._session_service, - self._auth_session, - self._config + DatafeedApi(self._agent_client), self._session_service, self._auth_session, self._config ) def get_datahose_loop(self) -> AbstractDatahoseLoop: @@ -179,7 +178,7 @@ def get_datahose_loop(self) -> AbstractDatahoseLoop: DatafeedApi(self._agent_client), self._session_service, self._auth_session, - self._config + self._config, ) def get_health_service(self) -> HealthService: @@ -188,9 +187,7 @@ def get_health_service(self) -> HealthService: :return: a new HealthService instance """ return HealthService( - AgentSystemApi(self._agent_client), - SignalsApi(self._agent_client), - self._config.retry + AgentSystemApi(self._agent_client), SignalsApi(self._agent_client), self._config.retry ) def get_presence_service(self) -> PresenceService: @@ -199,9 +196,7 @@ def get_presence_service(self) -> PresenceService: :return: a new PresenceService instance """ return PresenceService( - PresenceApi(self._pod_client), - self._auth_session, - self._config.retry + PresenceApi(self._pod_client), self._auth_session, self._config.retry ) def get_agent_version_service(self) -> AgentVersionService: @@ -209,8 +204,7 @@ def get_agent_version_service(self) -> AgentVersionService: :return: a new AgentVersionService instance """ - return AgentVersionService(SignalsApi(self._agent_client), - self._config.retry) + return AgentVersionService(SignalsApi(self._agent_client), self._config.retry) class OboServiceFactory: @@ -225,10 +219,7 @@ class OboServiceFactory: """ def __init__( - self, - api_client_factory: ApiClientFactory, - auth_session: AuthSession, - config: BdkConfig + self, api_client_factory: ApiClientFactory, auth_session: AuthSession, config: BdkConfig ): self._pod_client = api_client_factory.get_pod_client() self._agent_client = api_client_factory.get_agent_client() @@ -244,7 +235,7 @@ def get_user_service(self) -> OboUserService: UserApi(self._pod_client), UsersApi(self._pod_client), self._auth_session, - self._config.retry + self._config.retry, ) def get_message_service(self) -> OboMessageService: @@ -257,7 +248,7 @@ def get_message_service(self) -> OboMessageService: MessageSuppressionApi(self._pod_client), self._pod_client, self._auth_session, - self._config.retry + self._config.retry, ) def get_connection_service(self) -> OboConnectionService: @@ -266,9 +257,7 @@ def get_connection_service(self) -> OboConnectionService: :return: a new OboConnectionService instance. """ return OboConnectionService( - ConnectionApi(self._pod_client), - self._auth_session, - self._config.retry + ConnectionApi(self._pod_client), self._auth_session, self._config.retry ) def get_stream_service(self) -> OboStreamService: @@ -281,7 +270,8 @@ def get_stream_service(self) -> OboStreamService: RoomMembershipApi(self._pod_client), ShareApi(self._agent_client), self._auth_session, - self._config.retry) + self._config.retry, + ) def get_presence_service(self) -> OboPresenceService: """Returns a fully initialized OboPresenceService @@ -289,9 +279,7 @@ def get_presence_service(self) -> OboPresenceService: :return: a new OboPresenceService instance """ return OboPresenceService( - PresenceApi(self._pod_client), - self._auth_session, - self._config.retry + PresenceApi(self._pod_client), self._auth_session, self._config.retry ) def get_signal_service(self) -> OboSignalService: @@ -300,7 +288,5 @@ def get_signal_service(self) -> OboSignalService: :return: a new OboSignalService instance """ return OboSignalService( - SignalsApi(self._agent_client), - self._auth_session, - self._config.retry + SignalsApi(self._agent_client), self._auth_session, self._config.retry ) diff --git a/symphony/bdk/core/symphony_bdk.py b/symphony/bdk/core/symphony_bdk.py index 25f82675..2b9b3186 100644 --- a/symphony/bdk/core/symphony_bdk.py +++ b/symphony/bdk/core/symphony_bdk.py @@ -1,5 +1,5 @@ -"""Module containing SymphonyBdk class which acts as en entry point for all BDK services. -""" +"""Module containing SymphonyBdk class which acts as en entry point for all BDK services.""" + import functools import logging @@ -9,7 +9,7 @@ from symphony.bdk.core.auth.exception import AuthInitializationError from symphony.bdk.core.auth.ext_app_authenticator import ExtensionAppAuthenticator from symphony.bdk.core.client.api_client_factory import ApiClientFactory -from symphony.bdk.core.config.exception import BotNotConfiguredError, BdkConfigError +from symphony.bdk.core.config.exception import BdkConfigError, BotNotConfiguredError from symphony.bdk.core.extension import ExtensionService from symphony.bdk.core.service.application.application_service import ApplicationService from symphony.bdk.core.service.connection.connection_service import ConnectionService @@ -55,6 +55,7 @@ def app_service(func): :return: the value returned by the decorated function with the passed arguments. :raise: BdkConfigError if the app is not configured. """ + @functools.wraps(func) def check_if_app_configured_and_call_function(*args, **kwds): symphony_bdk = args[0] @@ -66,8 +67,7 @@ def check_if_app_configured_and_call_function(*args, **kwds): class SymphonyBdk: - """BDK entry point - """ + """BDK entry point""" async def __aenter__(self): return self @@ -80,7 +80,9 @@ def __init__(self, config): self._api_client_factory = ApiClientFactory(config) self._pod_client = self._api_client_factory.get_pod_client() self._agent_client = self._api_client_factory.get_agent_client() - self._authenticator_factory = AuthenticatorFactory(config, api_client_factory=self._api_client_factory) + self._authenticator_factory = AuthenticatorFactory( + config, api_client_factory=self._api_client_factory + ) self._bot_session = None self._ext_app_authenticator = None @@ -101,13 +103,17 @@ def __init__(self, config): if self._config.bot.is_authentication_configured(): self._initialize_bot_services() else: - logging.info("Bot (service account) credentials have not been configured." - "You can however use services in OBO mode if app authentication is configured.") + logging.info( + "Bot (service account) credentials have not been configured." + "You can however use services in OBO mode if app authentication is configured." + ) def _initialize_bot_services(self): bot_authenticator = self._authenticator_factory.get_bot_authenticator() self._bot_session = AuthSession(bot_authenticator) - self._service_factory = ServiceFactory(self._api_client_factory, self._bot_session, self._config) + self._service_factory = ServiceFactory( + self._api_client_factory, self._bot_session, self._config + ) self._user_service = self._service_factory.get_user_service() self._message_service = self._service_factory.get_message_service() self._connection_service = self._service_factory.get_connection_service() @@ -123,7 +129,9 @@ def _initialize_bot_services(self): self._activity_registry = ActivityRegistry(self._session_service) self._datafeed_loop.subscribe(self._activity_registry) # initialises extension service and register decorated extensions - self._extension_service = ExtensionService(self._api_client_factory, self._bot_session, self._config) + self._extension_service = ExtensionService( + self._api_client_factory, self._bot_session, self._config + ) bot_authenticator.agent_version_service = self._service_factory.get_agent_version_service() @bot_service @@ -142,7 +150,9 @@ def app_authenticator(self) -> ExtensionAppAuthenticator: :return: a new instance of :class:`symphony.bdk.core.auth.ext_app_authenticator.ExtensionAppAuthenticator` """ if self._ext_app_authenticator is None: - self._ext_app_authenticator = self._authenticator_factory.get_extension_app_authenticator() + self._ext_app_authenticator = ( + self._authenticator_factory.get_extension_app_authenticator() + ) return self._ext_app_authenticator @app_service @@ -152,11 +162,16 @@ def obo(self, user_id: int = None, username: str = None) -> OboAuthSession: :return: The obo authentication session """ if user_id is not None: - return self._authenticator_factory.get_obo_authenticator().authenticate_by_user_id(user_id) + return self._authenticator_factory.get_obo_authenticator().authenticate_by_user_id( + user_id + ) if username is not None: - return self._authenticator_factory.get_obo_authenticator().authenticate_by_username(username) - raise AuthInitializationError("At least user_id or username should be given to OBO authenticate the " - "extension app") + return self._authenticator_factory.get_obo_authenticator().authenticate_by_username( + username + ) + raise AuthInitializationError( + "At least user_id or username should be given to OBO authenticate the extension app" + ) @app_service def obo_services(self, obo_session: OboAuthSession) -> OboServices: @@ -272,6 +287,5 @@ def extensions(self) -> ExtensionService: return self._extension_service async def close_clients(self): - """Close all the existing api clients created by the api client factory. - """ + """Close all the existing api clients created by the api client factory.""" await self._api_client_factory.close_clients() diff --git a/symphony/bdk/ext/group.py b/symphony/bdk/ext/group.py index a8f92368..1a7e9664 100644 --- a/symphony/bdk/ext/group.py +++ b/symphony/bdk/ext/group.py @@ -1,10 +1,14 @@ from typing import AsyncGenerator -from symphony.bdk.core.extension import BdkAuthenticationAware, BdkApiClientFactoryAware, BdkExtensionServiceProvider, \ - BdkConfigAware +from symphony.bdk.core.extension import ( + BdkApiClientFactoryAware, + BdkAuthenticationAware, + BdkConfigAware, + BdkExtensionServiceProvider, +) from symphony.bdk.core.retry import retry from symphony.bdk.core.retry.strategy import is_network_or_minor_error, is_unauthorized -from symphony.bdk.core.service.pagination import offset_based_pagination, cursor_based_pagination +from symphony.bdk.core.service.pagination import cursor_based_pagination from symphony.bdk.core.service.user.user_util import extract_tenant_id from symphony.bdk.gen.group_api.group_api import GroupApi from symphony.bdk.gen.group_model.add_member import AddMember @@ -34,10 +38,10 @@ async def refresh_bearer_token_if_unauthorized(retry_state): return False -class SymphonyGroupBdkExtension(BdkAuthenticationAware, BdkApiClientFactoryAware, BdkConfigAware, - BdkExtensionServiceProvider): - """Extension for Symphony Groups APIs - """ +class SymphonyGroupBdkExtension( + BdkAuthenticationAware, BdkApiClientFactoryAware, BdkConfigAware, BdkExtensionServiceProvider +): + """Extension for Symphony Groups APIs""" def __init__(self): self._api_client_factory = None @@ -58,15 +62,15 @@ def get_service(self): class SymphonyGroupService: - """Service class for managing Symphony Groups - """ + """Service class for managing Symphony Groups""" def __init__(self, api_client_factory, auth_session, retry_config): self._api_client_factory = api_client_factory self._auth_session = auth_session self._retry_config = retry_config - self._oauth_session = OAuthSession(self._api_client_factory.get_login_client(), self._auth_session, - self._retry_config) + self._oauth_session = OAuthSession( + self._api_client_factory.get_login_client(), self._auth_session, self._retry_config + ) client = self._api_client_factory.get_client("/profile-manager") client.configuration.auth_settings = self._oauth_session.get_auth_settings @@ -85,8 +89,14 @@ async def insert_group(self, create_group: CreateGroup) -> ReadGroup: return await self._group_api.insert_group(x_symphony_host="", create_group=create_group) @retry(retry=refresh_bearer_token_if_unauthorized) - async def list_groups(self, status: Status = None, before: str = None, after: str = None, limit: int = None, - sort_order: SortOrder = None) -> GroupList: + async def list_groups( + self, + status: Status = None, + before: str = None, + after: str = None, + limit: int = None, + sort_order: SortOrder = None, + ) -> GroupList: """List groups of type SDL See: `List all groups of specified type `_ @@ -112,10 +122,7 @@ async def list_groups(self, status: Status = None, before: str = None, after: st return await self._group_api.list_groups(**kwargs) async def list_all_groups( - self, - status: Status = None, - chunk_size: int = 100, - max_number: int = None + self, status: Status = None, chunk_size: int = 100, max_number: int = None ) -> AsyncGenerator[ReadGroup, None]: """Returns an asynchronous generator of groups of type SDL See: `List all groups of specified type `_ @@ -128,15 +135,15 @@ async def list_all_groups( """ async def groups_one_page(limit, after=None): - result = await self.list_groups(status=status, - limit=limit, - after=after) - return result.data, getattr(result.pagination.cursors, 'after', None) + result = await self.list_groups(status=status, limit=limit, after=after) + return result.data, getattr(result.pagination.cursors, "after", None) return cursor_based_pagination(groups_one_page, chunk_size, max_number) @retry(retry=refresh_bearer_token_if_unauthorized) - async def update_group(self, if_match: str, group_id: str, update_group: UpdateGroup) -> ReadGroup: + async def update_group( + self, if_match: str, group_id: str, update_group: UpdateGroup + ) -> ReadGroup: """Update an existing group See: `Update a group `_ @@ -145,8 +152,9 @@ async def update_group(self, if_match: str, group_id: str, update_group: UpdateG :param update_group: the group fields to be updated :return: the updated group """ - return await self._group_api.update_group(x_symphony_host="", if_match=if_match, group_id=group_id, - update_group=update_group) + return await self._group_api.update_group( + x_symphony_host="", if_match=if_match, group_id=group_id, update_group=update_group + ) @retry(retry=refresh_bearer_token_if_unauthorized) async def update_avatar(self, group_id: str, image: str) -> ReadGroup: @@ -159,7 +167,9 @@ async def update_avatar(self, group_id: str, image: str) -> ReadGroup: :return: the updated group """ upload_avatar = UploadAvatar(image=image) - return await self._group_api.update_avatar(x_symphony_host="", group_id=group_id, upload_avatar=upload_avatar) + return await self._group_api.update_avatar( + x_symphony_host="", group_id=group_id, upload_avatar=upload_avatar + ) @retry(retry=refresh_bearer_token_if_unauthorized) async def get_group(self, group_id: str) -> ReadGroup: @@ -181,13 +191,13 @@ async def add_member_to_group(self, group_id: str, user_id: int) -> ReadGroup: :return: the updated group """ member = Member(member_id=user_id, member_tenant=extract_tenant_id(user_id)) - return await self._group_api.add_member_to_group(x_symphony_host="", group_id=group_id, - add_member=AddMember(member=member)) + return await self._group_api.add_member_to_group( + x_symphony_host="", group_id=group_id, add_member=AddMember(member=member) + ) class OAuthSession: - """Used to handle the bearer token needed to call Groups endpoints. - """ + """Used to handle the bearer token needed to call Groups endpoints.""" def __init__(self, login_client, session, retry_config): self._authentication_api = AuthenticationApi(login_client) @@ -197,10 +207,10 @@ def __init__(self, login_client, session, retry_config): @retry async def refresh(self): - """Refreshes internal Bearer authentication token from the bot sessionToken. - """ - jwt_token = await self._authentication_api.idm_tokens_post(await self._auth_session.session_token, - scope="profile-manager") + """Refreshes internal Bearer authentication token from the bot sessionToken.""" + jwt_token = await self._authentication_api.idm_tokens_post( + await self._auth_session.session_token, scope="profile-manager" + ) self._bearer_token = jwt_token.access_token async def get_auth_settings(self): @@ -208,12 +218,17 @@ async def get_auth_settings(self): :return: the map of auth_settings containing the header value with the current bearer token """ - return {"bearerAuth": {"in": "header", "type": "bearer", "key": "Authorization", - "value": "Bearer " + await self._get_bearer_token()}} + return { + "bearerAuth": { + "in": "header", + "type": "bearer", + "key": "Authorization", + "value": "Bearer " + await self._get_bearer_token(), + } + } async def _get_bearer_token(self): - """Returns the bearer token - """ + """Returns the bearer token""" if self._bearer_token is None: await self.refresh() return self._bearer_token diff --git a/tests/bdk/gen/rest_test.py b/tests/bdk/gen/rest_test.py index f67075f0..95362511 100644 --- a/tests/bdk/gen/rest_test.py +++ b/tests/bdk/gen/rest_test.py @@ -1,6 +1,6 @@ import pytest -from symphony.bdk.gen import rest, Configuration +from symphony.bdk.gen import Configuration, rest @pytest.mark.asyncio diff --git a/tests/bdk/integration/e2e_test.py b/tests/bdk/integration/e2e_test.py index 83a3fc71..7a8464a1 100644 --- a/tests/bdk/integration/e2e_test.py +++ b/tests/bdk/integration/e2e_test.py @@ -1,3 +1,4 @@ +# ruff: noqa import asyncio from datetime import datetime, timedelta from uuid import uuid4 @@ -8,32 +9,39 @@ from symphony.bdk.core.config.loader import BdkConfigLoader from symphony.bdk.core.symphony_bdk import SymphonyBdk from symphony.bdk.gen.pod_model.v3_room_attributes import V3RoomAttributes -from tests.bdk.integration.helpers import (BOT_USER_ID, FEED_BOT_USERNAME, - MSG_BOT_USERNAME, STREAM_ID, - SYMPHONY_HOST, TEST_RSA_KEY, - TEST_USER_ID, MessageListener, - datafeed_bot_config, - get_test_messages, - messenger_bot_config, send_messages) +from tests.bdk.integration.helpers import ( + BOT_USER_ID, + FEED_BOT_USERNAME, + MSG_BOT_USERNAME, + STREAM_ID, + SYMPHONY_HOST, + TEST_RSA_KEY, + TEST_USER_ID, + MessageListener, + datafeed_bot_config, + get_test_messages, + messenger_bot_config, + send_messages, +) -pytestmark =[ +pytestmark = [ pytest.mark.asyncio, pytest.mark.e2e, - pytest.mark.skipif( - not all( - [ - STREAM_ID, - MSG_BOT_USERNAME, - FEED_BOT_USERNAME, - SYMPHONY_HOST, - TEST_RSA_KEY, - TEST_USER_ID, - BOT_USER_ID, - ] + pytest.mark.skipif( + not all( + [ + STREAM_ID, + MSG_BOT_USERNAME, + FEED_BOT_USERNAME, + SYMPHONY_HOST, + TEST_RSA_KEY, + TEST_USER_ID, + BOT_USER_ID, + ] + ), + reason="Required environment variables for integration tests are not set " + "(STREAM_ID, MSG_BOT_USERNAME, FEED_BOT_USERNAME, SYMPHONY_HOST, TEST_RSA_KEY, TEST_USER_ID, BOT_USER_ID)", ), - reason="Required environment variables for integration tests are not set " - "(STREAM_ID, MSG_BOT_USERNAME, FEED_BOT_USERNAME, SYMPHONY_HOST, TEST_RSA_KEY, TEST_USER_ID, BOT_USER_ID)", -) ] NUMBER_OF_MESSAGES = 3 diff --git a/tests/bdk/integration/helpers.py b/tests/bdk/integration/helpers.py index 240a9b1e..8a6fa7df 100644 --- a/tests/bdk/integration/helpers.py +++ b/tests/bdk/integration/helpers.py @@ -1,15 +1,14 @@ import asyncio import os import re +from pathlib import Path import pytest import pytest_asyncio import yaml -from pathlib import Path from symphony.bdk.core.config.loader import BdkConfigLoader -from symphony.bdk.core.service.datafeed.real_time_event_listener import \ - RealTimeEventListener +from symphony.bdk.core.service.datafeed.real_time_event_listener import RealTimeEventListener from symphony.bdk.core.symphony_bdk import SymphonyBdk from symphony.bdk.gen.agent_model.v4_initiator import V4Initiator from symphony.bdk.gen.agent_model.v4_message_sent import V4MessageSent @@ -61,16 +60,12 @@ async def bdk(messenger_bot_config): async def send_messages(messages, stream_id, since, uuid): for i in range(NUMBER_OF_MESSAGES): - await messages.send_message( - stream_id, f"{uuid}-{i}-{since}" - ) + await messages.send_message(stream_id, f"{uuid}-{i}-{since}") async def get_test_messages(bdk, since, uuid): messages = await bdk.messages().list_messages(STREAM_ID, since=since) - cleaned_messages_text = [ - re.sub(r"<[^>]+>", " ", msg["message"]).strip() for msg in messages - ] + cleaned_messages_text = [re.sub(r"<[^>]+>", " ", msg["message"]).strip() for msg in messages] return list( filter( lambda msg: msg.startswith(uuid), diff --git a/tests/conftest.py b/tests/conftest.py index e7ff4be5..cb234f24 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,10 +6,9 @@ from unittest.mock import patch import pytest - from cryptography import x509 from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization, hashes +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.x509 import NameOID diff --git a/tests/core/activity/command_test.py b/tests/core/activity/command_test.py index bd8fca8b..549cf11b 100644 --- a/tests/core/activity/command_test.py +++ b/tests/core/activity/command_test.py @@ -44,12 +44,16 @@ def create_message_sent(message): def create_message_sent_with_data(content, data="{}"): return V4MessageSent( - message=V4Message(attachments=[], - message_id=MESSAGE_ID, - message="

    " - + content + "

    ", - stream=V4Stream(stream_id=STREAM_ID), - data=data)) + message=V4Message( + attachments=[], + message_id=MESSAGE_ID, + message='

    ' + + content + + "

    ", + stream=V4Stream(stream_id=STREAM_ID), + data=data, + ) + ) def test_matcher(activity, message_sent): @@ -103,26 +107,32 @@ def test_slash_command_matches_with_bot_mention(): context = create_command_context( create_message_sent_with_data( - content=f"

    " - f"@{BOT_NAME}{command}

    ", - data=f"{{\"0\":{{\"id\":[{{\"type\":\"com.symphony.user.userId\",\"value\":\"{BOT_USER_ID}\"}}]," - "\"type\":\"com.symphony.user.mention\"}}")) + content=f'

    ' + f"@{BOT_NAME}{command}

    ", + data=f'{{"0":{{"id":[{{"type":"com.symphony.user.userId","value":"{BOT_USER_ID}"}}],' + '"type":"com.symphony.user.mention"}}', + ) + ) assert slash_command.matches(context) context = create_command_context( create_message_sent_with_data( - content=f"

    " - f"@{BOT_NAME}{other_command}

    ", - data=f"{{\"0\":{{\"id\":[{{\"type\":\"com.symphony.user.userId\",\"value\":\"{BOT_USER_ID}\"}}]," - "\"type\":\"com.symphony.user.mention\"}}")) + content=f'

    ' + f"@{BOT_NAME}{other_command}

    ", + data=f'{{"0":{{"id":[{{"type":"com.symphony.user.userId","value":"{BOT_USER_ID}"}}],' + '"type":"com.symphony.user.mention"}}', + ) + ) assert not slash_command.matches(context) context = create_command_context( create_message_sent_with_data( - content=f"

    " - f"@other_bot_name{other_command}

    ", - data=f"{{\"0\":{{\"id\":[{{\"type\":\"com.symphony.user.userId\",\"value\":\"{other_bot_user_id}\"}}]," - "\"type\":\"com.symphony.user.mention\"}}")) + content=f'

    ' + f"@other_bot_name{other_command}

    ", + data=f'{{"0":{{"id":[{{"type":"com.symphony.user.userId","value":"{other_bot_user_id}"}}],' + '"type":"com.symphony.user.mention"}}', + ) + ) assert not slash_command.matches(context) @@ -132,23 +142,23 @@ def test_slash_command_matches_without_bot_mention(): slash_command = SlashCommandActivity(command, False, AsyncMock()) context = create_command_context( - create_message_sent_with_data( - content=f"

    {command}

    ", - data="{}")) + create_message_sent_with_data(content=f"

    {command}

    ", data="{}") + ) assert slash_command.matches(context) context = create_command_context( create_message_sent_with_data( - content=f"

    " - f"@{BOT_NAME}{command}

    ", - data=f"{{\"0\":{{\"id\":[{{\"type\":\"com.symphony.user.userId\",\"value\":\"{BOT_USER_ID}\"}}]," - "\"type\":\"com.symphony.user.mention\"}}")) + content=f'

    ' + f"@{BOT_NAME}{command}

    ", + data=f'{{"0":{{"id":[{{"type":"com.symphony.user.userId","value":"{BOT_USER_ID}"}}],' + '"type":"com.symphony.user.mention"}}', + ) + ) assert not slash_command.matches(context) context = create_command_context( - create_message_sent_with_data( - content=f"

    {other_command}

    ", - data="{}")) + create_message_sent_with_data(content=f"

    {other_command}

    ", data="{}") + ) assert not slash_command.matches(context) diff --git a/tests/core/activity/form_test.py b/tests/core/activity/form_test.py index fe5feeb0..d1827f41 100644 --- a/tests/core/activity/form_test.py +++ b/tests/core/activity/form_test.py @@ -15,9 +15,12 @@ class TestFormReplyActivity(FormReplyActivity): @pytest.fixture(name="context") def fixture_context(): - return FormReplyContext(V4Initiator(), V4SymphonyElementsAction(form_id="test_form", - form_message_id="message_id", - form_values={"key": "value"})) + return FormReplyContext( + V4Initiator(), + V4SymphonyElementsAction( + form_id="test_form", form_message_id="message_id", form_values={"key": "value"} + ), + ) def test_matcher(activity, context): diff --git a/tests/core/activity/help_command_test.py b/tests/core/activity/help_command_test.py index 8a5c0679..9442ba78 100644 --- a/tests/core/activity/help_command_test.py +++ b/tests/core/activity/help_command_test.py @@ -2,7 +2,7 @@ import pytest -from symphony.bdk.core.activity.command import SlashCommandActivity, CommandContext +from symphony.bdk.core.activity.command import CommandContext, SlashCommandActivity from symphony.bdk.core.activity.help_command import HelpCommand from symphony.bdk.core.activity.registry import ActivityRegistry from symphony.bdk.core.service.message.message_service import MessageService @@ -40,24 +40,33 @@ def fixture_bdk(activity_registry): @pytest.fixture(name="command_context") def fixture_command_context(): - message_content = f"
    " \ - f"

    " \ - f"

    " \ - f"

    " \ - f"@bot_name " \ - f" /help

    " - data = f"{{\"0\":{{\"id\":[{{\"type\":\"com.symphony.user.userId\",\"value\":\"{BOT_USER_ID}\"}}]," \ - f"\"type\":\"com.symphony.user.mention\"}}}}" - message_sent = V4MessageSent(message=V4Message(attachments=[], - message_id="message_id", - message=message_content, - data=data, - stream=V4Stream(stream_id=STREAM_ID))) + message_content = ( + '
    ' + "

    " + "

    " + "

    " + '@bot_name ' + " /help

    " + ) + data = ( + f'{{"0":{{"id":[{{"type":"com.symphony.user.userId","value":"{BOT_USER_ID}"}}],' + f'"type":"com.symphony.user.mention"}}}}' + ) + message_sent = V4MessageSent( + message=V4Message( + attachments=[], + message_id="message_id", + message=message_content, + data=data, + stream=V4Stream(stream_id=STREAM_ID), + ) + ) return CommandContext( initiator=V4Initiator(), source_event=message_sent, bot_display_name="bot_name", - bot_user_id=BOT_USER_ID) + bot_user_id=BOT_USER_ID, + ) @pytest.fixture(name="help_command") @@ -81,13 +90,17 @@ async def test_help_command(bdk, activity_registry, help_command, command_contex assert isinstance(help_activity, HelpCommand) assert help_activity.matches(command_context) await help_activity.on_activity(command_context) - message = "
    • /hello - (mention required)
    • " \ - "
    • /help - List available commands (mention required)
    " + message = ( + "
    • /hello - (mention required)
    • " + "
    • /help - List available commands (mention required)
    " + ) bdk.messages().send_message.assert_called_once_with(STREAM_ID, message) @pytest.mark.asyncio -async def test_help_command_no_other_commands_found(bdk, activity_registry, help_command, command_context): +async def test_help_command_no_other_commands_found( + bdk, activity_registry, help_command, command_context +): activity_registry.register(help_command) assert len(activity_registry.activity_list) == 1 @@ -100,7 +113,9 @@ async def test_help_command_no_other_commands_found(bdk, activity_registry, help @pytest.mark.asyncio -async def test_override_help_command_with_slash_help(bdk, activity_registry, help_command, command_context): +async def test_override_help_command_with_slash_help( + bdk, activity_registry, help_command, command_context +): listener = AsyncMock() activity_registry.slash("/help")(listener) @@ -116,7 +131,9 @@ async def test_override_help_command_with_slash_help(bdk, activity_registry, hel @pytest.mark.asyncio -async def test_override_slash_help_with_help_command(bdk, activity_registry, help_command, command_context): +async def test_override_slash_help_with_help_command( + bdk, activity_registry, help_command, command_context +): listener = AsyncMock() activity_registry.register(help_command) diff --git a/tests/core/activity/parsing/arguments_test.py b/tests/core/activity/parsing/arguments_test.py index 25bbd0b6..471c4bfb 100644 --- a/tests/core/activity/parsing/arguments_test.py +++ b/tests/core/activity/parsing/arguments_test.py @@ -1,15 +1,17 @@ import pytest from symphony.bdk.core.activity.parsing.arguments import Arguments -from symphony.bdk.core.activity.parsing.message_entities import Mention, Hashtag, Cashtag +from symphony.bdk.core.activity.parsing.message_entities import Cashtag, Hashtag, Mention @pytest.fixture(name="arguments_dict") def fixture_arguments_dict(): - return {"string_arg": "string_arg_value", - "mention_arg": Mention("@Jane Dow", 12345), - "hashtag_arg": Hashtag("#my_hashtag", "my_hashtag"), - "cashtag_arg": Cashtag("$my_cashtag", "my_cashtag")} + return { + "string_arg": "string_arg_value", + "mention_arg": Mention("@Jane Dow", 12345), + "hashtag_arg": Hashtag("#my_hashtag", "my_hashtag"), + "cashtag_arg": Cashtag("$my_cashtag", "my_cashtag"), + } @pytest.fixture(name="arguments") @@ -18,7 +20,9 @@ def fixture_arguments(arguments_dict): def test_get_argument_names(arguments): - assert ["string_arg", "mention_arg", "hashtag_arg", "cashtag_arg"] == list(arguments.get_argument_names()) + assert ["string_arg", "mention_arg", "hashtag_arg", "cashtag_arg"] == list( + arguments.get_argument_names() + ) def test_get_argument(arguments): diff --git a/tests/core/activity/parsing/input_tokenizer_test.py b/tests/core/activity/parsing/input_tokenizer_test.py index 2070dd82..ac568de3 100644 --- a/tests/core/activity/parsing/input_tokenizer_test.py +++ b/tests/core/activity/parsing/input_tokenizer_test.py @@ -1,5 +1,5 @@ -from symphony.bdk.core.activity.parsing.message_entities import Cashtag, Hashtag, Mention from symphony.bdk.core.activity.parsing.input_tokenizer import InputTokenizer +from symphony.bdk.core.activity.parsing.message_entities import Cashtag, Hashtag, Mention from symphony.bdk.gen.agent_model.v4_message import V4Message @@ -30,27 +30,30 @@ def test_words_inside_tags(): def test_one_mention(): under_test = build_tokenizer_with_data( - "@jane-doe", - "{\"0\":{\"id\":[{\"type\":\"com.symphony.user.userId\",\"value\":\"12345678\"}]," - "\"type\":\"com.symphony.user.mention\"}}") + '@jane-doe', + '{"0":{"id":[{"type":"com.symphony.user.userId","value":"12345678"}],' + '"type":"com.symphony.user.mention"}}', + ) assert under_test.tokens == [Mention("@jane-doe", 12345678)] assert under_test.tokens[0].user_display_name == "jane-doe" def test_mention_without_data(): - under_test = build_tokenizer("@jane-doe") - assert under_test.tokens == ["@jane-doe"], \ + under_test = build_tokenizer('@jane-doe') + assert under_test.tokens == ["@jane-doe"], ( "The mentions is handled as a string argument since no json entity provided" + ) def test_two_mentions_with_space(): under_test = build_tokenizer_with_data( - "
    @jane-doe " - "@John Doe
    ", - "{\"0\":{\"id\":[{\"type\":\"com.symphony.user.userId\",\"value\":\"12345678\"}]," - "\"type\":\"com.symphony.user.mention\"},\"1\":{\"id\":[{\"type\":\"com.symphony.user.userId\"," - "\"value\":\"12345679\"}],\"type\":\"com.symphony.user.mention\"}}") + '
    @jane-doe ' + '@John Doe
    ', + '{"0":{"id":[{"type":"com.symphony.user.userId","value":"12345678"}],' + '"type":"com.symphony.user.mention"},"1":{"id":[{"type":"com.symphony.user.userId",' + '"value":"12345679"}],"type":"com.symphony.user.mention"}}', + ) assert under_test.tokens == [Mention("@jane-doe", 12345678), Mention("@John Doe", 12345679)] assert under_test.tokens[0].user_display_name == "jane-doe" @@ -59,11 +62,12 @@ def test_two_mentions_with_space(): def test_two_mentions_without_space(): under_test = build_tokenizer_with_data( - "
    @jane-doe" - "@John Doe
    ", - "{\"0\":{\"id\":[{\"type\":\"com.symphony.user.userId\",\"value\":\"12345678\"}]," - "\"type\":\"com.symphony.user.mention\"},\"1\":{\"id\":[{\"type\":\"com.symphony.user.userId\"," - "\"value\":\"12345679\"}],\"type\":\"com.symphony.user.mention\"}}") + '
    @jane-doe' + '@John Doe
    ', + '{"0":{"id":[{"type":"com.symphony.user.userId","value":"12345678"}],' + '"type":"com.symphony.user.mention"},"1":{"id":[{"type":"com.symphony.user.userId",' + '"value":"12345679"}],"type":"com.symphony.user.mention"}}', + ) assert under_test.tokens == [Mention("@jane-doe", 12345678), Mention("@John Doe", 12345679)] assert under_test.tokens[0].user_display_name == "jane-doe" @@ -72,9 +76,10 @@ def test_two_mentions_without_space(): def test_text_and_one_mention(): under_test = build_tokenizer_with_data( - "
    lorem@jane-doe
    ", - "{\"0\":{\"id\":[{\"type\":\"com.symphony.user.userId\",\"value\":\"12345678\"}]," - "\"type\":\"com.symphony.user.mention\"}}") + '
    lorem@jane-doe
    ', + '{"0":{"id":[{"type":"com.symphony.user.userId","value":"12345678"}],' + '"type":"com.symphony.user.mention"}}', + ) assert under_test.tokens == ["lorem", Mention("@jane-doe", 12345678)] assert under_test.tokens[1].user_display_name == "jane-doe" @@ -82,50 +87,59 @@ def test_text_and_one_mention(): def test_text_and_two_mentions(): under_test = build_tokenizer_with_data( - "
    lorem@jane-doe" - "@jane-doe-two
    ", - "{\"0\":{\"id\":[{\"type\":\"com.symphony.user.userId\",\"value\":\"12345678\"}]," - "\"type\":\"com.symphony.user.mention\"}," - "\"1\":{\"id\":[{\"type\":\"com.symphony.user.userId\",\"value\":\"12345679\"}]," - "\"type\":\"com.symphony.user.mention\"}}") - - assert under_test.tokens == ["lorem", Mention("@jane-doe", 12345678), Mention("@jane-doe-two", 12345679)] + '
    lorem@jane-doe' + '@jane-doe-two
    ', + '{"0":{"id":[{"type":"com.symphony.user.userId","value":"12345678"}],' + '"type":"com.symphony.user.mention"},' + '"1":{"id":[{"type":"com.symphony.user.userId","value":"12345679"}],' + '"type":"com.symphony.user.mention"}}', + ) + + assert under_test.tokens == [ + "lorem", + Mention("@jane-doe", 12345678), + Mention("@jane-doe-two", 12345679), + ] assert under_test.tokens[1].user_display_name == "jane-doe" assert under_test.tokens[2].user_display_name == "jane-doe-two" def test_one_hashtag(): under_test = build_tokenizer_with_data( - "
    #myhashtag
    ", - "{\"0\":{\"id\":[{\"type\":\"org.symphonyoss.taxonomy.hashtag\",\"value\":\"hashtag\"}]," - "\"type\":\"org.symphonyoss.taxonomy\"}}") + '
    #myhashtag
    ', + '{"0":{"id":[{"type":"org.symphonyoss.taxonomy.hashtag","value":"hashtag"}],' + '"type":"org.symphonyoss.taxonomy"}}', + ) assert under_test.tokens == [Hashtag("#myhashtag", "hashtag")] def test_words_and_hashtag(): under_test = build_tokenizer_with_data( - "
    mytext1#myhashtag mytext2
    ", - "{\"0\":{\"id\":[{\"type\":\"org.symphonyoss.taxonomy.hashtag\",\"value\":\"hashtag\"}]," - "\"type\":\"org.symphonyoss.taxonomy\"}}") + '
    mytext1#myhashtag mytext2
    ', + '{"0":{"id":[{"type":"org.symphonyoss.taxonomy.hashtag","value":"hashtag"}],' + '"type":"org.symphonyoss.taxonomy"}}', + ) assert under_test.tokens == ["mytext1", Hashtag("#myhashtag", "hashtag"), "mytext2"] def test_words_and_hashtag_with_paragraph(): under_test = build_tokenizer_with_data( - "
    mytext1#myhashtag

    mytext2

    ", - "{\"0\":{\"id\":[{\"type\":\"org.symphonyoss.taxonomy.hashtag\",\"value\":\"hashtag\"}]," - "\"type\":\"org.symphonyoss.taxonomy\"}}") + '
    mytext1#myhashtag

    mytext2

    ', + '{"0":{"id":[{"type":"org.symphonyoss.taxonomy.hashtag","value":"hashtag"}],' + '"type":"org.symphonyoss.taxonomy"}}', + ) assert under_test.tokens == ["mytext1", Hashtag("#myhashtag", "hashtag"), "mytext2"] def test_one_cashtag(): under_test = build_tokenizer_with_data( - "$mycashtag", - "{\"0\":{\"id\":[{\"type\":\"org.symphonyoss.fin.security.id.ticker\",\"value\":\"mycashtag\"}]," - "\"type\":\"org.symphonyoss.fin.security\"}}") + '$mycashtag', + '{"0":{"id":[{"type":"org.symphonyoss.fin.security.id.ticker","value":"mycashtag"}],' + '"type":"org.symphonyoss.fin.security"}}', + ) assert under_test.tokens == [Cashtag("$mycashtag", "mycashtag")] @@ -149,7 +163,10 @@ def build_tokenizer_with_data(content, data): def build_v4_message(content, data="{}"): - return V4Message(attachments=[], - message="

    " + content + - "

    ", - data=data) + return V4Message( + attachments=[], + message='

    ' + + content + + "

    ", + data=data, + ) diff --git a/tests/core/activity/parsing/slash_command_pattern_test.py b/tests/core/activity/parsing/slash_command_pattern_test.py index 96dbcaab..04a3c469 100644 --- a/tests/core/activity/parsing/slash_command_pattern_test.py +++ b/tests/core/activity/parsing/slash_command_pattern_test.py @@ -1,11 +1,15 @@ import pytest -from symphony.bdk.core.activity.parsing.message_entities import Cashtag, Hashtag, Mention +from symphony.bdk.core.activity.parsing.command_token import ( + CashArgumentCommandToken, + HashArgumentCommandToken, + MentionArgumentCommandToken, + StaticCommandToken, + StringArgumentCommandToken, +) from symphony.bdk.core.activity.parsing.match_result import MatchResult +from symphony.bdk.core.activity.parsing.message_entities import Cashtag, Hashtag, Mention from symphony.bdk.core.activity.parsing.slash_command_pattern import SlashCommandPattern -from symphony.bdk.core.activity.parsing.command_token import StaticCommandToken, StringArgumentCommandToken, \ - HashArgumentCommandToken, CashArgumentCommandToken, MentionArgumentCommandToken -from symphony.bdk.gen.agent_model.v4_message import V4Message from tests.core.activity.parsing.input_tokenizer_test import build_v4_message @@ -70,8 +74,11 @@ def test_slash_command_two_static_strings_with_spaces(): def test_slash_command_with_two_string_arguments(): pattern = SlashCommandPattern("/command {arg1} {arg2}") - assert pattern.tokens == [StaticCommandToken("/command"), StringArgumentCommandToken("{arg1}"), - StringArgumentCommandToken("{arg2}")] + assert pattern.tokens == [ + StaticCommandToken("/command"), + StringArgumentCommandToken("{arg1}"), + StringArgumentCommandToken("{arg2}"), + ] assert pattern.tokens[0].pattern == "^/command$" assert pattern.tokens[1].arg_name == "arg1" assert pattern.tokens[2].arg_name == "arg2" @@ -125,9 +132,13 @@ def test_slash_command_bad_mention_command_argument(): def test_string_argument_command(): pattern = SlashCommandPattern("/command {string_arg} {@mention} {#hashtag} {$cashtag}") - assert pattern.tokens == [StaticCommandToken("/command"), StringArgumentCommandToken("{string_arg}"), - MentionArgumentCommandToken("{@mention}"), HashArgumentCommandToken("{#hashtag}"), - CashArgumentCommandToken("{$cashtag}")] + assert pattern.tokens == [ + StaticCommandToken("/command"), + StringArgumentCommandToken("{string_arg}"), + MentionArgumentCommandToken("{@mention}"), + HashArgumentCommandToken("{#hashtag}"), + CashArgumentCommandToken("{$cashtag}"), + ] assert pattern.tokens[0].pattern == "^/command$" assert pattern.tokens[1].arg_name == "string_arg" @@ -136,22 +147,28 @@ def test_string_argument_command(): assert pattern.tokens[4].arg_name == "cashtag" v4_message = build_v4_message( - content="/command my_argument
    @jane-doe
    " - "
    #myhashtag
    " - "
    $mycashtag
    ", - data="{\"0\":{\"id\":[{\"type\":\"com.symphony.user.userId\",\"value\":\"12345678\"}]," - "\"type\":\"com.symphony.user.mention\"}," - "\"1\":{\"id\":[{\"type\":\"org.symphonyoss.taxonomy.hashtag\",\"value\":\"hashtag\"}]," - "\"type\":\"org.symphonyoss.taxonomy\"}," - "\"2\":{\"id\":[{\"type\":\"org.symphonyoss.fin.security.id.ticker\",\"value\":\"cashtag\"}]," - "\"type\":\"org.symphonyoss.fin.security\"}" - "}") + content='/command my_argument
    @jane-doe
    ' + '
    #myhashtag
    ' + '
    $mycashtag
    ', + data='{"0":{"id":[{"type":"com.symphony.user.userId","value":"12345678"}],' + '"type":"com.symphony.user.mention"},' + '"1":{"id":[{"type":"org.symphonyoss.taxonomy.hashtag","value":"hashtag"}],' + '"type":"org.symphonyoss.taxonomy"},' + '"2":{"id":[{"type":"org.symphonyoss.fin.security.id.ticker","value":"cashtag"}],' + '"type":"org.symphonyoss.fin.security"}' + "}", + ) match_result = pattern.get_match_result(v4_message) - expected_match_result = MatchResult(True, {"string_arg": "my_argument", - "mention": Mention("@jane-doe", 12345678), - "hashtag": Hashtag("#myhashtag", "hashtag"), - "cashtag": Cashtag("$mycashtag", "cashtag")}) + expected_match_result = MatchResult( + True, + { + "string_arg": "my_argument", + "mention": Mention("@jane-doe", 12345678), + "hashtag": Hashtag("#myhashtag", "hashtag"), + "cashtag": Cashtag("$mycashtag", "cashtag"), + }, + ) assert expected_match_result == match_result @@ -159,10 +176,10 @@ def test_string_argument_command(): def test_match_result_hashtag(): pattern = SlashCommandPattern("{#hashtag}") v4_message = build_v4_message( - content="#myhashtag", - - data="{\"0\":{\"id\":[{\"type\":\"org.symphonyoss.taxonomy.hashtag\",\"value\":\"hashtag\"}]," - "\"type\":\"org.symphonyoss.taxonomy\"}}") + content='#myhashtag', + data='{"0":{"id":[{"type":"org.symphonyoss.taxonomy.hashtag","value":"hashtag"}],' + '"type":"org.symphonyoss.taxonomy"}}', + ) match_result = pattern.get_match_result(v4_message) expected_match_result = MatchResult(True, {"hashtag": Hashtag("#myhashtag", "hashtag")}) @@ -173,10 +190,10 @@ def test_match_result_hashtag(): def test_match_result_cashtag(): pattern = SlashCommandPattern("{$cashtag}") v4_message = build_v4_message( - content="$mycashtag", - - data="{\"0\":{\"id\":[{\"type\":\"org.symphonyoss.fin.security.id.ticker\",\"value\":\"cashtag\"}]," - "\"type\":\"org.symphonyoss.fin.security\"}}") + content='$mycashtag', + data='{"0":{"id":[{"type":"org.symphonyoss.fin.security.id.ticker","value":"cashtag"}],' + '"type":"org.symphonyoss.fin.security"}}', + ) match_result = pattern.get_match_result(v4_message) expected_match_result = MatchResult(True, {"cashtag": Cashtag("$mycashtag", "cashtag")}) @@ -187,21 +204,27 @@ def test_match_result_cashtag(): def test_match_result_mention_hashtag_cashtag(): pattern = SlashCommandPattern("/command {@mention} {#hashtag} {$cashtag}") v4_message = build_v4_message( - content="
    /command
    @jane-doe
    " - "
    #myhashtag
    " - "
    $mycashtag
    ", - data="{\"0\":{\"id\":[{\"type\":\"com.symphony.user.userId\",\"value\":\"12345678\"}]," - "\"type\":\"com.symphony.user.mention\"}," - "\"1\":{\"id\":[{\"type\":\"org.symphonyoss.taxonomy.hashtag\",\"value\":\"hashtag\"}]," - "\"type\":\"org.symphonyoss.taxonomy\"}," - "\"2\":{\"id\":[{\"type\":\"org.symphonyoss.fin.security.id.ticker\",\"value\":\"cashtag\"}]," - "\"type\":\"org.symphonyoss.fin.security\"}" - "}") + content='
    /command
    @jane-doe
    ' + '
    #myhashtag
    ' + '
    $mycashtag
    ', + data='{"0":{"id":[{"type":"com.symphony.user.userId","value":"12345678"}],' + '"type":"com.symphony.user.mention"},' + '"1":{"id":[{"type":"org.symphonyoss.taxonomy.hashtag","value":"hashtag"}],' + '"type":"org.symphonyoss.taxonomy"},' + '"2":{"id":[{"type":"org.symphonyoss.fin.security.id.ticker","value":"cashtag"}],' + '"type":"org.symphonyoss.fin.security"}' + "}", + ) match_result = pattern.get_match_result(v4_message) - expected_match_result = MatchResult(True, {"mention": Mention("@jane-doe", 12345678), - "hashtag": Hashtag("#myhashtag", "hashtag"), - "cashtag": Cashtag("$mycashtag", "cashtag")}) + expected_match_result = MatchResult( + True, + { + "mention": Mention("@jane-doe", 12345678), + "hashtag": Hashtag("#myhashtag", "hashtag"), + "cashtag": Cashtag("$mycashtag", "cashtag"), + }, + ) assert expected_match_result == match_result @@ -220,9 +243,10 @@ def test_match_result_one_static_token_one_argument(): def test_one_mention_not_matching_string_pattern(): pattern = SlashCommandPattern("{argument_not_mention}") v4_message = build_v4_message( - content="@jane-doe", - data="{\"0\":{\"id\":[{\"type\":\"com.symphony.user.userId\",\"value\":\"12345678\"}]," - "\"type\":\"com.symphony.user.mention\"}}") + content='@jane-doe', + data='{"0":{"id":[{"type":"com.symphony.user.userId","value":"12345678"}],' + '"type":"com.symphony.user.mention"}}', + ) match_result = pattern.get_match_result(v4_message) assert not match_result.is_matching @@ -241,9 +265,10 @@ def test_one_string_argument_not_matching_mention(): def test_one_hashtag_not_matching_string_pattern(): pattern = SlashCommandPattern("{argument_not_hashtag}") v4_message = build_v4_message( - content="#hash_tag_not_string", - data="{\"0\":{\"id\":[{\"type\":\"org.symphonyoss.taxonomy.hashtag\",\"value\":\"hashtag\"}]," - "\"type\":\"org.symphonyoss.taxonomy\"}}") + content='#hash_tag_not_string', + data='{"0":{"id":[{"type":"org.symphonyoss.taxonomy.hashtag","value":"hashtag"}],' + '"type":"org.symphonyoss.taxonomy"}}', + ) match_result = pattern.get_match_result(v4_message) assert not match_result.is_matching @@ -262,9 +287,10 @@ def test_one_string_argument_not_matching_hashtag(): def test_one_cashtag_not_matching_string_pattern(): pattern = SlashCommandPattern("{argument_not_hashtag}") v4_message = build_v4_message( - content="$cash_tag_not_string", - data="{\"0\":{\"id\":[{\"type\":\"org.symphonyoss.fin.security.id.ticker\",\"value\":\"cashtag\"}]," - "\"type\":\"org.symphonyoss.fin.security\"}}") + content='$cash_tag_not_string', + data='{"0":{"id":[{"type":"org.symphonyoss.fin.security.id.ticker","value":"cashtag"}],' + '"type":"org.symphonyoss.fin.security"}}', + ) match_result = pattern.get_match_result(v4_message) assert not match_result.is_matching diff --git a/tests/core/activity/registry_test.py b/tests/core/activity/registry_test.py index ccdfd397..2a667da5 100644 --- a/tests/core/activity/registry_test.py +++ b/tests/core/activity/registry_test.py @@ -1,21 +1,23 @@ -from unittest.mock import MagicMock, AsyncMock +from unittest.mock import AsyncMock, MagicMock import pytest -from symphony.bdk.core.activity.parsing.command_token import MatchingUserIdMentionToken, StaticCommandToken -from symphony.bdk.gen.agent_model.v4_user import V4User - from symphony.bdk.core.activity.command import CommandActivity, SlashCommandActivity from symphony.bdk.core.activity.form import FormReplyActivity +from symphony.bdk.core.activity.parsing.command_token import ( + MatchingUserIdMentionToken, + StaticCommandToken, +) from symphony.bdk.core.activity.registry import ActivityRegistry +from symphony.bdk.core.activity.user_joined_room import UserJoinedRoomActivity from symphony.bdk.core.service.session.session_service import SessionService from symphony.bdk.gen.agent_model.v4_initiator import V4Initiator from symphony.bdk.gen.agent_model.v4_message import V4Message from symphony.bdk.gen.agent_model.v4_message_sent import V4MessageSent from symphony.bdk.gen.agent_model.v4_stream import V4Stream -from symphony.bdk.core.activity.user_joined_room import UserJoinedRoomActivity -from symphony.bdk.gen.agent_model.v4_user_joined_room import V4UserJoinedRoom from symphony.bdk.gen.agent_model.v4_symphony_elements_action import V4SymphonyElementsAction +from symphony.bdk.gen.agent_model.v4_user import V4User +from symphony.bdk.gen.agent_model.v4_user_joined_room import V4UserJoinedRoom @pytest.fixture(name="session_service") @@ -42,22 +44,24 @@ def fixture_user(): @pytest.fixture(name="message_sent") def fixture_message_sent(): - return V4MessageSent(message=V4Message(attachments=[], - message_id="message_id", - message="

    hello world

    ", - stream=V4Stream(stream_id="stream_id"))) + return V4MessageSent( + message=V4Message( + attachments=[], + message_id="message_id", + message="

    hello world

    ", + stream=V4Stream(stream_id="stream_id"), + ) + ) @pytest.fixture(name="elements_action") def fixture_elements_action(): - return V4SymphonyElementsAction(form_id="test_form", - form_values={"key": "value"}) + return V4SymphonyElementsAction(form_id="test_form", form_values={"key": "value"}) @pytest.fixture(name="user_joined_room") def fixture_user_joined_room(): - return V4UserJoinedRoom(stream=V4Stream(stream_id="12345678"), - affected_user=V4User(user_id=0)) + return V4UserJoinedRoom(stream=V4Stream(stream_id="12345678"), affected_user=V4User(user_id=0)) @pytest.fixture(name="activity_registry") @@ -78,8 +82,9 @@ async def test_register(activity_registry, session_service, message_sent): @pytest.mark.asyncio -async def test_register_different_activities_instance(activity_registry, command, form, user, message_sent, - elements_action, user_joined_room): +async def test_register_different_activities_instance( + activity_registry, command, form, user, message_sent, elements_action, user_joined_room +): command.on_activity = AsyncMock() form.on_activity = AsyncMock() user.on_activity = AsyncMock() @@ -285,4 +290,3 @@ async def test_slash_same_command_name_different_mention(activity_registry, mess assert slash_activity2._name == command_name assert not slash_activity2._requires_mention_bot assert slash_activity2._callback == listener2 - diff --git a/tests/core/activity/user_joined_room_test.py b/tests/core/activity/user_joined_room_test.py index d3d41225..8457a34f 100644 --- a/tests/core/activity/user_joined_room_test.py +++ b/tests/core/activity/user_joined_room_test.py @@ -1,12 +1,14 @@ import pytest -from symphony.bdk.gen.agent_model.v4_user import V4User +from symphony.bdk.core.activity.user_joined_room import ( + UserJoinedRoomActivity, + UserJoinedRoomContext, +) from symphony.bdk.gen.agent_model.v4_initiator import V4Initiator from symphony.bdk.gen.agent_model.v4_stream import V4Stream +from symphony.bdk.gen.agent_model.v4_user import V4User from symphony.bdk.gen.agent_model.v4_user_joined_room import V4UserJoinedRoom -from symphony.bdk.core.activity.user_joined_room import UserJoinedRoomActivity, UserJoinedRoomContext - @pytest.fixture(name="activity") def fixture_activty(): @@ -18,9 +20,10 @@ class TestUserJoinedRoomActivity(UserJoinedRoomActivity): @pytest.fixture(name="context") def fixture_context(): - return UserJoinedRoomContext(V4Initiator(), - V4UserJoinedRoom(stream=V4Stream(stream_id="12345678"), - affected_user=V4User(user_id=0))) + return UserJoinedRoomContext( + V4Initiator(), + V4UserJoinedRoom(stream=V4Stream(stream_id="12345678"), affected_user=V4User(user_id=0)), + ) def test_matcher(activity, context): diff --git a/tests/core/auth/auth_session_test.py b/tests/core/auth/auth_session_test.py index de6f6c47..0c674819 100644 --- a/tests/core/auth/auth_session_test.py +++ b/tests/core/auth/auth_session_test.py @@ -1,25 +1,24 @@ from datetime import datetime, timezone - from unittest.mock import AsyncMock, MagicMock, patch import pytest from symphony.bdk.core.auth.auth_session import ( + SKD_FLAG_NAME, + AppAuthSession, AuthSession, OboAuthSession, - AppAuthSession, - SKD_FLAG_NAME, ) -from symphony.bdk.core.auth.exception import AuthInitializationError -from symphony.bdk.gen.login_model.token import Token -from symphony.bdk.gen.login_model.extension_app_tokens import ExtensionAppTokens from symphony.bdk.core.auth.bot_authenticator import BotAuthenticatorRsa +from symphony.bdk.core.auth.exception import AuthInitializationError from symphony.bdk.core.config.model.bdk_bot_config import BdkBotConfig from symphony.bdk.gen.api_client import ApiClient +from symphony.bdk.gen.login_model.extension_app_tokens import ExtensionAppTokens +from symphony.bdk.gen.login_model.token import Token + @pytest.fixture def mock_authenticator(): - config = MagicMock(spec=BdkBotConfig) login_client = MagicMock(spec=ApiClient) relay_client = MagicMock(spec=ApiClient) @@ -51,7 +50,10 @@ async def test_refresh(): @pytest.mark.asyncio async def test_auth_token(): mock_bot_authenticator = AsyncMock() - expired_ts, fresh_ts, = ( + ( + expired_ts, + fresh_ts, + ) = ( datetime.now(timezone.utc).timestamp(), datetime.now(timezone.utc).timestamp() + 500, ) @@ -127,21 +129,17 @@ async def test_app_auth_session(): expire_at = 1539636528288 ext_app_authenticator = AsyncMock() - ext_app_authenticator.authenticate_and_retrieve_tokens.return_value = ( - ExtensionAppTokens( - app_id="app_id", - app_token=retrieved_app_token, - symphony_token=symphony_token, - expire_at=expire_at, - ) + ext_app_authenticator.authenticate_and_retrieve_tokens.return_value = ExtensionAppTokens( + app_id="app_id", + app_token=retrieved_app_token, + symphony_token=symphony_token, + expire_at=expire_at, ) session = AppAuthSession(ext_app_authenticator, input_app_token) await session.refresh() - ext_app_authenticator.authenticate_and_retrieve_tokens.assert_called_once_with( - input_app_token - ) + ext_app_authenticator.authenticate_and_retrieve_tokens.assert_called_once_with(input_app_token) assert session.app_token == retrieved_app_token assert session.symphony_token == symphony_token assert session.expire_at == expire_at @@ -150,9 +148,7 @@ async def test_app_auth_session(): @pytest.mark.asyncio async def test_skd_disabled_if_claim_is_missing(auth_session): # Given: The token claims do not contain the SKD flag - with patch( - "symphony.bdk.core.auth.auth_session.extract_token_claims", return_value={} - ): + with patch("symphony.bdk.core.auth.auth_session.extract_token_claims", return_value={}): # When: skd_enabled is checked is_enabled = await auth_session.skd_enabled # Then: The result is False @@ -208,13 +204,9 @@ async def test_km_token_is_empty_when_skd_enabled(auth_session, mock_authenticat @pytest.mark.asyncio -async def test_km_token_is_retrieved_when_skd_disabled( - auth_session, mock_authenticator -): +async def test_km_token_is_retrieved_when_skd_disabled(auth_session, mock_authenticator): # Given: SKD is disabled because the token claim is missing - with patch( - "symphony.bdk.core.auth.auth_session.extract_token_claims", return_value={} - ): + with patch("symphony.bdk.core.auth.auth_session.extract_token_claims", return_value={}): # When: The key manager token is requested km_token = await auth_session.key_manager_token # Then: The real token is returned and the retrieval method was called diff --git a/tests/core/auth/authenticator_factory_test.py b/tests/core/auth/authenticator_factory_test.py index 709d90ac..cc405639 100644 --- a/tests/core/auth/authenticator_factory_test.py +++ b/tests/core/auth/authenticator_factory_test.py @@ -3,10 +3,13 @@ import pytest from symphony.bdk.core.auth.authenticator_factory import AuthenticatorFactory -from symphony.bdk.core.auth.bot_authenticator import BotAuthenticatorRsa, BotAuthenticatorCert +from symphony.bdk.core.auth.bot_authenticator import BotAuthenticatorCert, BotAuthenticatorRsa from symphony.bdk.core.auth.exception import AuthInitializationError -from symphony.bdk.core.auth.ext_app_authenticator import ExtensionAppAuthenticatorRsa, ExtensionAppAuthenticatorCert -from symphony.bdk.core.auth.obo_authenticator import OboAuthenticatorRsa, OboAuthenticatorCert +from symphony.bdk.core.auth.ext_app_authenticator import ( + ExtensionAppAuthenticatorCert, + ExtensionAppAuthenticatorRsa, +) +from symphony.bdk.core.auth.obo_authenticator import OboAuthenticatorCert, OboAuthenticatorRsa from symphony.bdk.core.client.api_client_factory import ApiClientFactory from symphony.bdk.core.config.loader import BdkConfigLoader from symphony.bdk.core.config.model.bdk_authentication_config import BdkAuthenticationConfig diff --git a/tests/core/auth/bot_authenticator_cert_test.py b/tests/core/auth/bot_authenticator_cert_test.py index a8d324a3..599e87a1 100644 --- a/tests/core/auth/bot_authenticator_cert_test.py +++ b/tests/core/auth/bot_authenticator_cert_test.py @@ -1,4 +1,5 @@ -from unittest.mock import MagicMock, AsyncMock +from unittest.mock import AsyncMock, MagicMock + import pytest from symphony.bdk.core.auth.bot_authenticator import BotAuthenticatorCert @@ -24,12 +25,7 @@ def _create_api_client(): # We do this to have a new instance for each call @pytest.fixture(name="config") def fixture_config(): - bot_config = { - "username": "test_bot", - "privateKey": { - "path": "path/to/private_key" - } - } + bot_config = {"username": "test_bot", "privateKey": {"path": "path/to/private_key"}} return BdkBotConfig(bot_config) @@ -41,7 +37,9 @@ async def test_bot_session_cert(mocked_api_client): session_auth_client.call_api.return_value = Token(token="session_token") key_auth_client.call_api.return_value = Token(token="km_token") - bot_authenticator = BotAuthenticatorCert(session_auth_client, key_auth_client, minimal_retry_config()) + bot_authenticator = BotAuthenticatorCert( + session_auth_client, key_auth_client, minimal_retry_config() + ) session_token = await bot_authenticator.retrieve_session_token() km_token = await bot_authenticator.retrieve_key_manager_token() @@ -57,7 +55,9 @@ async def test_api_exception_cert(mocked_api_client): session_auth_client.call_api.side_effect = ApiException(status=401) key_auth_client.call_api.side_effect = ApiException(status=401) - bot_authenticator = BotAuthenticatorCert(session_auth_client, key_auth_client, minimal_retry_config()) + bot_authenticator = BotAuthenticatorCert( + session_auth_client, key_auth_client, minimal_retry_config() + ) with pytest.raises(AuthUnauthorizedError): await bot_authenticator.retrieve_session_token() diff --git a/tests/core/auth/bot_authenticator_rsa_test.py b/tests/core/auth/bot_authenticator_rsa_test.py index 9adad6dd..91779e11 100644 --- a/tests/core/auth/bot_authenticator_rsa_test.py +++ b/tests/core/auth/bot_authenticator_rsa_test.py @@ -1,4 +1,4 @@ -from unittest.mock import patch, MagicMock, AsyncMock +from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -25,44 +25,56 @@ def _create_api_client(): # We do this to have a new instance for each call @pytest.fixture(name="config") def fixture_config(): - bot_config = { - "username": "test_bot", - "privateKey": { - "path": "path/to/private_key" - } - } + bot_config = {"username": "test_bot", "privateKey": {"path": "path/to/private_key"}} return BdkBotConfig(bot_config) @pytest.mark.asyncio async def test_bot_session_rsa(config, mocked_api_client): - with patch("symphony.bdk.core.auth.bot_authenticator.create_signed_jwt", return_value="privateKey"), patch("symphony.bdk.core.auth.bot_authenticator.generate_expiration_time", return_value=100): + with ( + patch( + "symphony.bdk.core.auth.bot_authenticator.create_signed_jwt", return_value="privateKey" + ), + patch( + "symphony.bdk.core.auth.bot_authenticator.generate_expiration_time", return_value=100 + ), + ): login_api_client = mocked_api_client() relay_api_client = mocked_api_client() - login_api_client.call_api.return_value = Token(authorization_token="auth_token", token="session_token", name="sessionToken") + login_api_client.call_api.return_value = Token( + authorization_token="auth_token", token="session_token", name="sessionToken" + ) relay_api_client.call_api.return_value = Token(token="km_token") - bot_authenticator = BotAuthenticatorRsa(config, login_api_client, relay_api_client, minimal_retry_config()) + bot_authenticator = BotAuthenticatorRsa( + config, login_api_client, relay_api_client, minimal_retry_config() + ) session_token = await bot_authenticator.retrieve_session_token() km_token = await bot_authenticator.retrieve_key_manager_token() auth_token, expire_at = await bot_authenticator.retrieve_session_token_object() assert session_token == "session_token" assert km_token == "km_token" - assert auth_token == Token(authorization_token="auth_token", token="session_token", name="sessionToken") + assert auth_token == Token( + authorization_token="auth_token", token="session_token", name="sessionToken" + ) assert expire_at == 100 @pytest.mark.asyncio async def test_api_exception_rsa(config, mocked_api_client): - with patch("symphony.bdk.core.auth.bot_authenticator.create_signed_jwt", return_value="privateKey"): + with patch( + "symphony.bdk.core.auth.bot_authenticator.create_signed_jwt", return_value="privateKey" + ): login_api_client = mocked_api_client() relay_api_client = mocked_api_client() login_api_client.call_api.side_effect = ApiException(status=401) relay_api_client.call_api.side_effect = ApiException(status=401) - bot_authenticator = BotAuthenticatorRsa(config, login_api_client, relay_api_client, minimal_retry_config()) + bot_authenticator = BotAuthenticatorRsa( + config, login_api_client, relay_api_client, minimal_retry_config() + ) with pytest.raises(AuthUnauthorizedError): await bot_authenticator.retrieve_session_token() diff --git a/tests/core/auth/ext_app_authenticator_test.py b/tests/core/auth/ext_app_authenticator_test.py index 30c3af2b..a61ece4c 100644 --- a/tests/core/auth/ext_app_authenticator_test.py +++ b/tests/core/auth/ext_app_authenticator_test.py @@ -3,10 +3,15 @@ import pytest from symphony.bdk.core.auth.auth_session import AppAuthSession -from symphony.bdk.core.auth.ext_app_authenticator import ExtensionAppAuthenticatorRsa, ExtensionAppAuthenticatorCert +from symphony.bdk.core.auth.ext_app_authenticator import ( + ExtensionAppAuthenticatorCert, + ExtensionAppAuthenticatorRsa, +) from symphony.bdk.core.config.model.bdk_rsa_key_config import BdkRsaKeyConfig from symphony.bdk.gen import ApiException -from symphony.bdk.gen.auth_model.extension_app_authenticate_request import ExtensionAppAuthenticateRequest +from symphony.bdk.gen.auth_model.extension_app_authenticate_request import ( + ExtensionAppAuthenticateRequest, +) from symphony.bdk.gen.login_model.extension_app_tokens import ExtensionAppTokens from symphony.bdk.gen.pod_model.pod_certificate import PodCertificate from tests.core.config import minimal_retry_config @@ -23,7 +28,9 @@ def new_ext_app_authenticator(): @pytest.mark.asyncio async def test_authenticate_extension_app_calls_refresh(): - with patch("symphony.bdk.core.auth.ext_app_authenticator.AppAuthSession.refresh") as mock_refresh: + with patch( + "symphony.bdk.core.auth.ext_app_authenticator.AppAuthSession.refresh" + ) as mock_refresh: ext_app_authenticator = new_ext_app_authenticator() auth_session = await ext_app_authenticator.authenticate_extension_app("app_token") @@ -36,8 +43,12 @@ async def test_authenticate_extension_app(ext_app_authenticator): app_token = "app_token" retrieved_app_token = "out_app_token" symphony_token = "sym_token" - tokens = ExtensionAppTokens(app_id="app_id", app_token=retrieved_app_token, symphony_token=symphony_token, - expire_at=12345) + tokens = ExtensionAppTokens( + app_id="app_id", + app_token=retrieved_app_token, + symphony_token=symphony_token, + expire_at=12345, + ) ext_app_authenticator.authenticate_and_retrieve_tokens = AsyncMock(return_value=tokens) auth_session = await ext_app_authenticator.authenticate_extension_app(app_token) @@ -58,27 +69,42 @@ async def test_authenticate_and_retrieve_tokens(): key_config = BdkRsaKeyConfig(content="key content") mock_create_jwt.return_value = signed_jwt - extension_app_tokens = ExtensionAppTokens(app_id="my_app_id", app_token=out_app_token, - symphony_token=symphony_token, - expire_at=12345) + extension_app_tokens = ExtensionAppTokens( + app_id="my_app_id", + app_token=out_app_token, + symphony_token=symphony_token, + expire_at=12345, + ) mock_authentication_api = AsyncMock() mock_authentication_api.v1_pubkey_app_authenticate_extension_app_post = AsyncMock( - return_value=extension_app_tokens) + return_value=extension_app_tokens + ) - extension_app_authenticator = ExtensionAppAuthenticatorRsa(mock_authentication_api, None, app_id, key_config, - minimal_retry_config()) - returned_ext_app_tokens = await extension_app_authenticator.authenticate_and_retrieve_tokens(app_token) + extension_app_authenticator = ExtensionAppAuthenticatorRsa( + mock_authentication_api, None, app_id, key_config, minimal_retry_config() + ) + returned_ext_app_tokens = ( + await extension_app_authenticator.authenticate_and_retrieve_tokens(app_token) + ) assert returned_ext_app_tokens == extension_app_tokens - assert extension_app_authenticator._tokens_repository._tokens == {out_app_token: symphony_token} + assert extension_app_authenticator._tokens_repository._tokens == { + out_app_token: symphony_token + } assert await extension_app_authenticator.is_token_pair_valid(out_app_token, symphony_token) - assert not await extension_app_authenticator.is_token_pair_valid("invalid app token", symphony_token) - assert not await extension_app_authenticator.is_token_pair_valid(out_app_token, "invalid symphony token") + assert not await extension_app_authenticator.is_token_pair_valid( + "invalid app token", symphony_token + ) + assert not await extension_app_authenticator.is_token_pair_valid( + out_app_token, "invalid symphony token" + ) mock_create_jwt.assert_called_once_with(key_config, app_id) mock_authentication_api.v1_pubkey_app_authenticate_extension_app_post.assert_called_once() - call_args = mock_authentication_api.v1_pubkey_app_authenticate_extension_app_post.call_args.args[0] + call_args = ( + mock_authentication_api.v1_pubkey_app_authenticate_extension_app_post.call_args.args[0] + ) assert call_args.app_token == app_token assert call_args.auth_token == signed_jwt @@ -90,10 +116,13 @@ async def test_authenticate_and_retrieve_tokens_failure(): mock_create_jwt.return_value = "signed jwt" mock_authentication_api = AsyncMock() - mock_authentication_api.v1_pubkey_app_authenticate_extension_app_post = AsyncMock(side_effect=ValueError) + mock_authentication_api.v1_pubkey_app_authenticate_extension_app_post = AsyncMock( + side_effect=ValueError + ) - extension_app_authenticator = ExtensionAppAuthenticatorRsa(mock_authentication_api, None, "app_id", key_config, - minimal_retry_config()) + extension_app_authenticator = ExtensionAppAuthenticatorRsa( + mock_authentication_api, None, "app_id", key_config, minimal_retry_config() + ) with pytest.raises(ValueError): await extension_app_authenticator.authenticate_and_retrieve_tokens("app_token") @@ -108,8 +137,9 @@ async def test_validate_jwt_get_pod_certificate_failure(): mock_pod_api.v1_podcert_get = AsyncMock(side_effect=ApiException(status=400)) with pytest.raises(ApiException): - ext_app_authenticator = ExtensionAppAuthenticatorRsa(None, mock_pod_api, "app-id", None, - minimal_retry_config()) + ext_app_authenticator = ExtensionAppAuthenticatorRsa( + None, mock_pod_api, "app-id", None, minimal_retry_config() + ) await ext_app_authenticator.validate_jwt("my-jwt") mock_pod_api.v1_podcert_get.assert_called_once() @@ -124,9 +154,13 @@ async def test_validate_jwt(): app_id = "app-id" mock_pod_api = AsyncMock() - mock_pod_api.v1_podcert_get = AsyncMock(return_value=PodCertificate(certificate=certificate_content)) + mock_pod_api.v1_podcert_get = AsyncMock( + return_value=PodCertificate(certificate=certificate_content) + ) - ext_app_authenticator = ExtensionAppAuthenticatorRsa(None, mock_pod_api, app_id, None, minimal_retry_config()) + ext_app_authenticator = ExtensionAppAuthenticatorRsa( + None, mock_pod_api, app_id, None, minimal_retry_config() + ) await ext_app_authenticator.validate_jwt(jwt) mock_pod_api.v1_podcert_get.assert_called_once() @@ -140,24 +174,35 @@ async def test_cert_authenticate_and_retrieve_tokens(): out_app_token = "out_app_token" symphony_token = "symphony_token" - extension_app_tokens = ExtensionAppTokens(app_id="my_app_id", app_token=out_app_token, - symphony_token=symphony_token, - expire_at=12345) + extension_app_tokens = ExtensionAppTokens( + app_id="my_app_id", app_token=out_app_token, symphony_token=symphony_token, expire_at=12345 + ) mock_authentication_api = AsyncMock() - mock_authentication_api.v1_authenticate_extension_app_post = AsyncMock(return_value=extension_app_tokens) + mock_authentication_api.v1_authenticate_extension_app_post = AsyncMock( + return_value=extension_app_tokens + ) - extension_app_authenticator = ExtensionAppAuthenticatorCert(mock_authentication_api, None, app_id, - minimal_retry_config()) - returned_ext_app_tokens = await extension_app_authenticator.authenticate_and_retrieve_tokens(app_token) + extension_app_authenticator = ExtensionAppAuthenticatorCert( + mock_authentication_api, None, app_id, minimal_retry_config() + ) + returned_ext_app_tokens = await extension_app_authenticator.authenticate_and_retrieve_tokens( + app_token + ) assert returned_ext_app_tokens == extension_app_tokens assert extension_app_authenticator._tokens_repository._tokens == {out_app_token: symphony_token} assert await extension_app_authenticator.is_token_pair_valid(out_app_token, symphony_token) - assert not await extension_app_authenticator.is_token_pair_valid("invalid app token", symphony_token) - assert not await extension_app_authenticator.is_token_pair_valid(out_app_token, "invalid symphony token") + assert not await extension_app_authenticator.is_token_pair_valid( + "invalid app token", symphony_token + ) + assert not await extension_app_authenticator.is_token_pair_valid( + out_app_token, "invalid symphony token" + ) expected_request = ExtensionAppAuthenticateRequest(app_token=app_token) - mock_authentication_api.v1_authenticate_extension_app_post.assert_called_once_with(auth_request=expected_request) + mock_authentication_api.v1_authenticate_extension_app_post.assert_called_once_with( + auth_request=expected_request + ) @pytest.mark.asyncio @@ -169,9 +214,12 @@ async def test_cert_validate_jwt(): mock_pod_api = AsyncMock() mock_pod_api.v1_app_pod_certificate_get = AsyncMock( - return_value=PodCertificate(certificate=certificate_content)) + return_value=PodCertificate(certificate=certificate_content) + ) - ext_app_authenticator = ExtensionAppAuthenticatorCert(None, mock_pod_api, app_id, minimal_retry_config()) + ext_app_authenticator = ExtensionAppAuthenticatorCert( + None, mock_pod_api, app_id, minimal_retry_config() + ) await ext_app_authenticator.validate_jwt(jwt) mock_pod_api.v1_app_pod_certificate_get.assert_called_once() diff --git a/tests/core/auth/jwt_helper_test.py b/tests/core/auth/jwt_helper_test.py index 8b8f2685..b3d016cf 100644 --- a/tests/core/auth/jwt_helper_test.py +++ b/tests/core/auth/jwt_helper_test.py @@ -7,9 +7,9 @@ from symphony.bdk.core.auth.exception import AuthInitializationError from symphony.bdk.core.auth.jwt_helper import ( create_signed_jwt, - validate_jwt, create_signed_jwt_with_claims, extract_token_claims, + validate_jwt, ) from symphony.bdk.core.config.model.bdk_rsa_key_config import BdkRsaKeyConfig diff --git a/tests/core/auth/obo_authenticator_test.py b/tests/core/auth/obo_authenticator_test.py index a884417f..e0bea3e1 100644 --- a/tests/core/auth/obo_authenticator_test.py +++ b/tests/core/auth/obo_authenticator_test.py @@ -2,7 +2,7 @@ import pytest -from symphony.bdk.core.auth.obo_authenticator import OboAuthenticatorRsa, OboAuthenticatorCert +from symphony.bdk.core.auth.obo_authenticator import OboAuthenticatorCert, OboAuthenticatorRsa from symphony.bdk.core.config.model.bdk_app_config import BdkAppConfig from symphony.bdk.gen.auth_model.obo_auth_response import OboAuthResponse from symphony.bdk.gen.exceptions import ApiException @@ -13,12 +13,7 @@ @pytest.fixture(name="config") def fixture_config(): - app_config = { - "appId": "test_bot", - "privateKey": { - "path": "path/to/private_key" - } - } + app_config = {"appId": "test_bot", "privateKey": {"path": "path/to/private_key"}} return BdkAppConfig(app_config) @@ -29,19 +24,30 @@ async def test_obo_session_username(config): session_token = "session_token" username = "username" - with patch("symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value=signed_jwt) as create_jwt, \ - patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api: + with ( + patch( + "symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value=signed_jwt + ) as create_jwt, + patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api, + ): auth_api.pubkey_app_authenticate_post = AsyncMock(return_value=Token(token=app_token)) - auth_api.pubkey_app_username_username_authenticate_post = AsyncMock(return_value=Token(token=session_token)) + auth_api.pubkey_app_username_username_authenticate_post = AsyncMock( + return_value=Token(token=session_token) + ) obo_authenticator = OboAuthenticatorRsa(config, auth_api, minimal_retry_config()) - retrieved_session_token = await obo_authenticator.retrieve_obo_session_token_by_username(username) + retrieved_session_token = await obo_authenticator.retrieve_obo_session_token_by_username( + username + ) assert retrieved_session_token == session_token create_jwt.assert_called_once_with(config.private_key, config.app_id) - auth_api.pubkey_app_authenticate_post.assert_called_once_with(AuthenticateRequest(token=signed_jwt)) - auth_api.pubkey_app_username_username_authenticate_post.assert_called_once_with(session_token=app_token, - username=username) + auth_api.pubkey_app_authenticate_post.assert_called_once_with( + AuthenticateRequest(token=signed_jwt) + ) + auth_api.pubkey_app_username_username_authenticate_post.assert_called_once_with( + session_token=app_token, username=username + ) @pytest.mark.asyncio @@ -51,25 +57,40 @@ async def test_obo_session_user_id(config): session_token = "session_token" user_id = 1234 - with patch("symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value=signed_jwt) as create_jwt, \ - patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api: + with ( + patch( + "symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value=signed_jwt + ) as create_jwt, + patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api, + ): auth_api.pubkey_app_authenticate_post = AsyncMock(return_value=Token(token=app_token)) - auth_api.pubkey_app_user_user_id_authenticate_post = AsyncMock(return_value=Token(token=session_token)) + auth_api.pubkey_app_user_user_id_authenticate_post = AsyncMock( + return_value=Token(token=session_token) + ) obo_authenticator = OboAuthenticatorRsa(config, auth_api, minimal_retry_config()) - retrieved_session_token = await obo_authenticator.retrieve_obo_session_token_by_user_id(user_id) + retrieved_session_token = await obo_authenticator.retrieve_obo_session_token_by_user_id( + user_id + ) assert retrieved_session_token == session_token create_jwt.assert_called_once_with(config.private_key, config.app_id) - auth_api.pubkey_app_authenticate_post.assert_called_once_with(AuthenticateRequest(token=signed_jwt)) - auth_api.pubkey_app_user_user_id_authenticate_post.assert_called_once_with(session_token=app_token, - user_id=user_id) + auth_api.pubkey_app_authenticate_post.assert_called_once_with( + AuthenticateRequest(token=signed_jwt) + ) + auth_api.pubkey_app_user_user_id_authenticate_post.assert_called_once_with( + session_token=app_token, user_id=user_id + ) @pytest.mark.asyncio async def test_api_exception(config): - with patch("symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value="signed_jwt"), \ - patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api: + with ( + patch( + "symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value="signed_jwt" + ), + patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api, + ): auth_api.pubkey_app_authenticate_post = AsyncMock(side_effect=ApiException(400)) obo_authenticator = OboAuthenticatorRsa(config, auth_api, minimal_retry_config()) @@ -83,10 +104,16 @@ async def test_api_exception(config): @pytest.mark.asyncio async def test_api_exception_in_authenticate_username(config): - with patch("symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value="signed_jwt"), \ - patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api: + with ( + patch( + "symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value="signed_jwt" + ), + patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api, + ): auth_api.pubkey_app_authenticate_post = AsyncMock(return_value=Token(token="app_token")) - auth_api.pubkey_app_username_username_authenticate_post = AsyncMock(side_effect=ApiException(400)) + auth_api.pubkey_app_username_username_authenticate_post = AsyncMock( + side_effect=ApiException(400) + ) obo_authenticator = OboAuthenticatorRsa(config, auth_api, minimal_retry_config()) @@ -96,10 +123,16 @@ async def test_api_exception_in_authenticate_username(config): @pytest.mark.asyncio async def test_api_exception_in_authenticate_userid(config): - with patch("symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value="signed_jwt"), \ - patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api: + with ( + patch( + "symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value="signed_jwt" + ), + patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api, + ): auth_api.pubkey_app_authenticate_post = AsyncMock(return_value=Token(token="app_token")) - auth_api.pubkey_app_user_user_id_authenticate_post = AsyncMock(side_effect=ApiException(400)) + auth_api.pubkey_app_user_user_id_authenticate_post = AsyncMock( + side_effect=ApiException(400) + ) obo_authenticator = OboAuthenticatorRsa(config, auth_api, minimal_retry_config()) @@ -114,19 +147,28 @@ async def test_authenticate_by_username(config): session_token = "session_token" username = "username" - with patch("symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value=signed_jwt) as create_jwt, \ - patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api: + with ( + patch( + "symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value=signed_jwt + ) as create_jwt, + patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api, + ): auth_api.pubkey_app_authenticate_post = AsyncMock(return_value=Token(token=app_token)) - auth_api.pubkey_app_username_username_authenticate_post = AsyncMock(return_value=Token(token=session_token)) + auth_api.pubkey_app_username_username_authenticate_post = AsyncMock( + return_value=Token(token=session_token) + ) obo_authenticator = OboAuthenticatorRsa(config, auth_api, minimal_retry_config()) obo_session = obo_authenticator.authenticate_by_username(username) assert await obo_session.session_token == session_token create_jwt.assert_called_once_with(config.private_key, config.app_id) - auth_api.pubkey_app_authenticate_post.assert_called_once_with(AuthenticateRequest(token=signed_jwt)) - auth_api.pubkey_app_username_username_authenticate_post.assert_called_once_with(session_token=app_token, - username=username) + auth_api.pubkey_app_authenticate_post.assert_called_once_with( + AuthenticateRequest(token=signed_jwt) + ) + auth_api.pubkey_app_username_username_authenticate_post.assert_called_once_with( + session_token=app_token, username=username + ) @pytest.mark.asyncio @@ -136,19 +178,28 @@ async def test_authenticate_by_user_id(config): session_token = "session_token" user_id = 1234 - with patch("symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value=signed_jwt) as create_jwt, \ - patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api: + with ( + patch( + "symphony.bdk.core.auth.obo_authenticator.create_signed_jwt", return_value=signed_jwt + ) as create_jwt, + patch("symphony.bdk.core.auth.obo_authenticator.AuthenticationApi") as auth_api, + ): auth_api.pubkey_app_authenticate_post = AsyncMock(return_value=Token(token=app_token)) - auth_api.pubkey_app_user_user_id_authenticate_post = AsyncMock(return_value=Token(token=session_token)) + auth_api.pubkey_app_user_user_id_authenticate_post = AsyncMock( + return_value=Token(token=session_token) + ) obo_authenticator = OboAuthenticatorRsa(config, auth_api, minimal_retry_config()) obo_session = obo_authenticator.authenticate_by_user_id(1234) assert await obo_session.session_token == "session_token" create_jwt.assert_called_once_with(config.private_key, config.app_id) - auth_api.pubkey_app_authenticate_post.assert_called_once_with(AuthenticateRequest(token=signed_jwt)) - auth_api.pubkey_app_user_user_id_authenticate_post.assert_called_once_with(session_token=app_token, - user_id=user_id) + auth_api.pubkey_app_authenticate_post.assert_called_once_with( + AuthenticateRequest(token=signed_jwt) + ) + auth_api.pubkey_app_user_user_id_authenticate_post.assert_called_once_with( + session_token=app_token, user_id=user_id + ) @pytest.mark.asyncio @@ -160,15 +211,19 @@ async def test_obo_session_username_cert_authentication(): auth_api.v1_app_authenticate_post = AsyncMock(return_value=Token(token=app_token)) auth_api.v1_app_username_username_authenticate_post = AsyncMock( - return_value=OboAuthResponse(session_token=session_token)) + return_value=OboAuthResponse(session_token=session_token) + ) obo_authenticator = OboAuthenticatorCert(auth_api, minimal_retry_config()) - retrieved_session_token = await obo_authenticator.retrieve_obo_session_token_by_username(username) + retrieved_session_token = await obo_authenticator.retrieve_obo_session_token_by_username( + username + ) assert retrieved_session_token == session_token auth_api.v1_app_authenticate_post.assert_called_once() - auth_api.v1_app_username_username_authenticate_post.assert_called_once_with(session_token=app_token, - username=username) + auth_api.v1_app_username_username_authenticate_post.assert_called_once_with( + session_token=app_token, username=username + ) @pytest.mark.asyncio @@ -180,14 +235,19 @@ async def test_obo_session_user_id_cert_authentication(): auth_api.v1_app_authenticate_post = AsyncMock(return_value=Token(token=app_token)) auth_api.v1_app_user_uid_authenticate_post = AsyncMock( - return_value=OboAuthResponse(session_token=session_token)) + return_value=OboAuthResponse(session_token=session_token) + ) obo_authenticator = OboAuthenticatorCert(auth_api, minimal_retry_config()) - retrieved_session_token = await obo_authenticator.retrieve_obo_session_token_by_user_id(user_id) + retrieved_session_token = await obo_authenticator.retrieve_obo_session_token_by_user_id( + user_id + ) assert retrieved_session_token == session_token auth_api.v1_app_authenticate_post.assert_called_once() - auth_api.v1_app_user_uid_authenticate_post.assert_called_once_with(session_token=app_token, uid=user_id) + auth_api.v1_app_user_uid_authenticate_post.assert_called_once_with( + session_token=app_token, uid=user_id + ) @pytest.mark.asyncio @@ -207,7 +267,9 @@ async def test_api_exception_cert_auth_in_app_authenticate(): async def test_api_exception_cert_auth_in_username_authenticate(): with patch("symphony.bdk.core.auth.obo_authenticator.CertificateAuthenticationApi") as auth_api: auth_api.v1_app_authenticate_post = AsyncMock(return_value=Token(token="app_token")) - auth_api.v1_app_username_username_authenticate_post = AsyncMock(side_effect=ApiException(400)) + auth_api.v1_app_username_username_authenticate_post = AsyncMock( + side_effect=ApiException(400) + ) obo_authenticator = OboAuthenticatorCert(auth_api, minimal_retry_config()) with pytest.raises(ApiException): @@ -234,7 +296,8 @@ async def test_authenticate_by_username_cert_authentication(): auth_api.v1_app_authenticate_post = AsyncMock(return_value=Token(token=app_token)) auth_api.v1_app_username_username_authenticate_post = AsyncMock( - return_value=OboAuthResponse(session_token=session_token)) + return_value=OboAuthResponse(session_token=session_token) + ) obo_authenticator = OboAuthenticatorCert(auth_api, minimal_retry_config()) obo_session = obo_authenticator.authenticate_by_username(username) @@ -242,8 +305,9 @@ async def test_authenticate_by_username_cert_authentication(): assert obo_session.username == username assert await obo_session.session_token == session_token auth_api.v1_app_authenticate_post.assert_called_once() - auth_api.v1_app_username_username_authenticate_post.assert_called_once_with(session_token=app_token, - username=username) + auth_api.v1_app_username_username_authenticate_post.assert_called_once_with( + session_token=app_token, username=username + ) @pytest.mark.asyncio @@ -255,7 +319,8 @@ async def test_authenticate_by_user_id_cert_authentication(): auth_api.v1_app_authenticate_post = AsyncMock(return_value=Token(token=app_token)) auth_api.v1_app_user_uid_authenticate_post = AsyncMock( - return_value=OboAuthResponse(session_token=session_token)) + return_value=OboAuthResponse(session_token=session_token) + ) obo_authenticator = OboAuthenticatorCert(auth_api, minimal_retry_config()) obo_session = obo_authenticator.authenticate_by_user_id(user_id) @@ -263,4 +328,6 @@ async def test_authenticate_by_user_id_cert_authentication(): assert obo_session.user_id == user_id assert await obo_session.session_token == session_token auth_api.v1_app_authenticate_post.assert_called_once() - auth_api.v1_app_user_uid_authenticate_post.assert_called_once_with(session_token=app_token, uid=user_id) + auth_api.v1_app_user_uid_authenticate_post.assert_called_once_with( + session_token=app_token, uid=user_id + ) diff --git a/tests/core/client/api_client_factory_test.py b/tests/core/client/api_client_factory_test.py index b17a1af8..f6a33139 100644 --- a/tests/core/client/api_client_factory_test.py +++ b/tests/core/client/api_client_factory_test.py @@ -3,8 +3,15 @@ import pytest -from symphony.bdk.core.client.api_client_factory import ApiClientFactory, POD, LOGIN, AGENT, SESSION_AUTH, RELAY, \ - KEY_AUTH +from symphony.bdk.core.client.api_client_factory import ( + AGENT, + KEY_AUTH, + LOGIN, + POD, + RELAY, + SESSION_AUTH, + ApiClientFactory, +) from symphony.bdk.core.config.model.bdk_config import BdkConfig from symphony.bdk.core.config.model.bdk_server_config import BdkProxyConfig from symphony.bdk.core.config.model.bdk_ssl_config import BdkSslConfig @@ -73,7 +80,10 @@ async def test_client_cert_configured_for_app(client_certificate_path, config): client_factory = ApiClientFactory(config) - assert client_factory.get_app_session_auth_client().configuration.cert_file == client_certificate_path + assert ( + client_factory.get_app_session_auth_client().configuration.cert_file + == client_certificate_path + ) @pytest.mark.asyncio @@ -82,7 +92,9 @@ async def test_client_cert_configured_for_bot(client_certificate_path, config): client_factory = ApiClientFactory(config) - assert client_factory.get_session_auth_client().configuration.cert_file == client_certificate_path + assert ( + client_factory.get_session_auth_client().configuration.cert_file == client_certificate_path + ) assert client_factory.get_key_auth_client().configuration.cert_file == client_certificate_path @@ -102,13 +114,24 @@ async def test_proxy_configured(config): client_factory = ApiClientFactory(config) assert_host_and_proxy_configured(client_factory.get_pod_client(), POD, proxy_host, proxy_port) - assert_host_and_proxy_configured(client_factory.get_login_client(), LOGIN, proxy_host, proxy_port) - assert_host_and_proxy_configured(client_factory.get_agent_client(), AGENT, proxy_host, proxy_port) - assert_host_and_proxy_configured(client_factory.get_session_auth_client(), SESSION_AUTH, proxy_host, proxy_port) - assert_host_and_proxy_configured(client_factory.get_key_auth_client(), KEY_AUTH, proxy_host, proxy_port) - assert_host_and_proxy_configured(client_factory.get_app_session_auth_client(), SESSION_AUTH, proxy_host, - proxy_port) - assert_host_and_proxy_configured(client_factory.get_relay_client(), RELAY, proxy_host, proxy_port) + assert_host_and_proxy_configured( + client_factory.get_login_client(), LOGIN, proxy_host, proxy_port + ) + assert_host_and_proxy_configured( + client_factory.get_agent_client(), AGENT, proxy_host, proxy_port + ) + assert_host_and_proxy_configured( + client_factory.get_session_auth_client(), SESSION_AUTH, proxy_host, proxy_port + ) + assert_host_and_proxy_configured( + client_factory.get_key_auth_client(), KEY_AUTH, proxy_host, proxy_port + ) + assert_host_and_proxy_configured( + client_factory.get_app_session_auth_client(), SESSION_AUTH, proxy_host, proxy_port + ) + assert_host_and_proxy_configured( + client_factory.get_relay_client(), RELAY, proxy_host, proxy_port + ) @pytest.mark.asyncio @@ -118,15 +141,27 @@ async def test_proxy_credentials_configured(config): config.proxy = BdkProxyConfig(proxy_host, proxy_port, "user", "pass") client_factory = ApiClientFactory(config) - assert_host_and_proxy_credentials_configured(client_factory.get_pod_client(), POD, proxy_host, proxy_port) - assert_host_and_proxy_credentials_configured(client_factory.get_login_client(), LOGIN, proxy_host, proxy_port) - assert_host_and_proxy_credentials_configured(client_factory.get_agent_client(), AGENT, proxy_host, proxy_port) - assert_host_and_proxy_credentials_configured(client_factory.get_session_auth_client(), SESSION_AUTH, proxy_host, - proxy_port) - assert_host_and_proxy_credentials_configured(client_factory.get_key_auth_client(), KEY_AUTH, proxy_host, proxy_port) - assert_host_and_proxy_credentials_configured(client_factory.get_app_session_auth_client(), SESSION_AUTH, proxy_host, - proxy_port) - assert_host_and_proxy_credentials_configured(client_factory.get_relay_client(), RELAY, proxy_host, proxy_port) + assert_host_and_proxy_credentials_configured( + client_factory.get_pod_client(), POD, proxy_host, proxy_port + ) + assert_host_and_proxy_credentials_configured( + client_factory.get_login_client(), LOGIN, proxy_host, proxy_port + ) + assert_host_and_proxy_credentials_configured( + client_factory.get_agent_client(), AGENT, proxy_host, proxy_port + ) + assert_host_and_proxy_credentials_configured( + client_factory.get_session_auth_client(), SESSION_AUTH, proxy_host, proxy_port + ) + assert_host_and_proxy_credentials_configured( + client_factory.get_key_auth_client(), KEY_AUTH, proxy_host, proxy_port + ) + assert_host_and_proxy_credentials_configured( + client_factory.get_app_session_auth_client(), SESSION_AUTH, proxy_host, proxy_port + ) + assert_host_and_proxy_credentials_configured( + client_factory.get_relay_client(), RELAY, proxy_host, proxy_port + ) @pytest.mark.asyncio @@ -215,12 +250,24 @@ async def test_global_default_headers(config): client_factory = ApiClientFactory(config) assert_default_headers(client_factory.get_pod_client().default_headers, config.default_headers) - assert_default_headers(client_factory.get_login_client().default_headers, config.default_headers) - assert_default_headers(client_factory.get_agent_client().default_headers, config.default_headers) - assert_default_headers(client_factory.get_session_auth_client().default_headers, config.default_headers) - assert_default_headers(client_factory.get_key_auth_client().default_headers, config.default_headers) - assert_default_headers(client_factory.get_app_session_auth_client().default_headers, config.default_headers) - assert_default_headers(client_factory.get_relay_client().default_headers, config.default_headers) + assert_default_headers( + client_factory.get_login_client().default_headers, config.default_headers + ) + assert_default_headers( + client_factory.get_agent_client().default_headers, config.default_headers + ) + assert_default_headers( + client_factory.get_session_auth_client().default_headers, config.default_headers + ) + assert_default_headers( + client_factory.get_key_auth_client().default_headers, config.default_headers + ) + assert_default_headers( + client_factory.get_app_session_auth_client().default_headers, config.default_headers + ) + assert_default_headers( + client_factory.get_relay_client().default_headers, config.default_headers + ) @pytest.mark.asyncio @@ -265,9 +312,13 @@ async def test_default_headers_at_session_auth_level(config): assert_default_headers(client_factory.get_pod_client().default_headers, {}) assert_default_headers(client_factory.get_login_client().default_headers, {}) assert_default_headers(client_factory.get_agent_client().default_headers, {}) - assert_default_headers(client_factory.get_session_auth_client().default_headers, default_headers) + assert_default_headers( + client_factory.get_session_auth_client().default_headers, default_headers + ) assert_default_headers(client_factory.get_key_auth_client().default_headers, {}) - assert_default_headers(client_factory.get_app_session_auth_client().default_headers, default_headers) + assert_default_headers( + client_factory.get_app_session_auth_client().default_headers, default_headers + ) assert_default_headers(client_factory.get_relay_client().default_headers, {}) @@ -289,7 +340,9 @@ async def test_default_headers_at_km_level(config): @pytest.mark.asyncio async def test_x_trace_id_not_in_default_headers(config, add_x_trace_id): - with patch("symphony.bdk.core.client.api_client_factory.add_x_trace_id", return_value=add_x_trace_id) as mock: + with patch( + "symphony.bdk.core.client.api_client_factory.add_x_trace_id", return_value=add_x_trace_id + ) as mock: client_factory = ApiClientFactory(config) assert mock.call_count == 7 @@ -307,16 +360,24 @@ async def test_x_trace_id_in_default_headers(config, add_x_trace_id): default_headers = {"x-trace-id": "trace-id"} config.default_headers = default_headers - with patch("symphony.bdk.core.client.api_client_factory.add_x_trace_id", return_value=add_x_trace_id) as mock: + with patch( + "symphony.bdk.core.client.api_client_factory.add_x_trace_id", return_value=add_x_trace_id + ) as mock: client_factory = ApiClientFactory(config) mock.assert_not_called() assert_default_headers(client_factory.get_pod_client().default_headers, default_headers) assert_default_headers(client_factory.get_login_client().default_headers, default_headers) assert_default_headers(client_factory.get_agent_client().default_headers, default_headers) - assert_default_headers(client_factory.get_session_auth_client().default_headers, default_headers) - assert_default_headers(client_factory.get_key_auth_client().default_headers, default_headers) - assert_default_headers(client_factory.get_app_session_auth_client().default_headers, default_headers) + assert_default_headers( + client_factory.get_session_auth_client().default_headers, default_headers + ) + assert_default_headers( + client_factory.get_key_auth_client().default_headers, default_headers + ) + assert_default_headers( + client_factory.get_app_session_auth_client().default_headers, default_headers + ) assert_default_headers(client_factory.get_relay_client().default_headers, default_headers) diff --git a/tests/core/client/trace_id_test.py b/tests/core/client/trace_id_test.py index 20e048f2..731efdec 100644 --- a/tests/core/client/trace_id_test.py +++ b/tests/core/client/trace_id_test.py @@ -3,7 +3,12 @@ import pytest -from symphony.bdk.core.client.trace_id import DistributedTracingContext, add_x_trace_id, TRACE_ID_LENGTH, X_TRACE_ID +from symphony.bdk.core.client.trace_id import ( + TRACE_ID_LENGTH, + X_TRACE_ID, + DistributedTracingContext, + add_x_trace_id, +) def setup_function(): diff --git a/tests/core/config/__init__.py b/tests/core/config/__init__.py index 6c4fa643..422d7fa2 100644 --- a/tests/core/config/__init__.py +++ b/tests/core/config/__init__.py @@ -2,14 +2,12 @@ def minimal_retry_config(): - return BdkRetryConfig(dict(maxAttempts=1, - initialIntervalMillis=10, - maxIntervalMillis=10, - multiplier=1)) + return BdkRetryConfig( + dict(maxAttempts=1, initialIntervalMillis=10, maxIntervalMillis=10, multiplier=1) + ) def minimal_retry_config_with_attempts(max_attempts: int): - return BdkRetryConfig(dict(maxAttempts=max_attempts, - initialIntervalMillis=10, - maxIntervalMillis=10, - multiplier=1)) + return BdkRetryConfig( + dict(maxAttempts=max_attempts, initialIntervalMillis=10, maxIntervalMillis=10, multiplier=1) + ) diff --git a/tests/core/config/bdk_config_loader_test.py b/tests/core/config/bdk_config_loader_test.py index 4f2c4f92..7d51db60 100644 --- a/tests/core/config/bdk_config_loader_test.py +++ b/tests/core/config/bdk_config_loader_test.py @@ -20,7 +20,9 @@ def fixture_global_config_path(request): return get_config_resource_filepath(request.param) -@pytest.fixture(name="wrong_path", params=["/wrong_path/config.json", "/wrong_path/wrong_extension.something"]) +@pytest.fixture( + name="wrong_path", params=["/wrong_path/config.json", "/wrong_path/wrong_extension.something"] +) def fixture_wrong_path(request): return request.param @@ -43,8 +45,10 @@ def test_load_from_file_not_found(wrong_path): BdkConfigLoader.load_from_file(wrong_path) -@pytest.mark.skipif(os.environ.get("CI") == "true", - reason="GitHub actions does not allow to create file in the home directory") +@pytest.mark.skipif( + os.environ.get("CI") == "true", + reason="GitHub actions does not allow to create file in the home directory", +) def test_load_from_symphony_directory(simple_config_path): tmp_config_filename = str(uuid.uuid4()) + "-config.yaml" tmp_config_path = Path.home() / ".symphony" / tmp_config_filename @@ -95,7 +99,9 @@ def test_load_client_global_config(global_config_path): def test_load_proxy_defined_at_global_level(): - config = BdkConfigLoader.load_from_file(get_config_resource_filepath("config_global_proxy.yaml")) + config = BdkConfigLoader.load_from_file( + get_config_resource_filepath("config_global_proxy.yaml") + ) assert config.proxy.host == "proxy.symphony.com" assert config.proxy.port == 1234 @@ -109,7 +115,9 @@ def test_load_proxy_defined_at_global_level(): def test_load_proxy_defined_at_global_and_child_level(): - config = BdkConfigLoader.load_from_file(get_config_resource_filepath("config_proxy_global_child.yaml")) + config = BdkConfigLoader.load_from_file( + get_config_resource_filepath("config_proxy_global_child.yaml") + ) assert config.proxy.host == "proxy.symphony.com" assert config.proxy.port == 1234 @@ -127,7 +135,9 @@ def test_load_proxy_defined_at_global_and_child_level(): def test_load_proxy_defined_at_child_level_only(): - config = BdkConfigLoader.load_from_file(get_config_resource_filepath("config_proxy_child_only.yaml")) + config = BdkConfigLoader.load_from_file( + get_config_resource_filepath("config_proxy_child_only.yaml") + ) assert config.proxy is None assert config.pod.proxy is None @@ -141,22 +151,32 @@ def test_load_proxy_defined_at_child_level_only(): def test_load_default_headers_defined_at_child_level_only(): - config = BdkConfigLoader.load_from_file(get_config_resource_filepath("config_headers_child_only.yaml")) + config = BdkConfigLoader.load_from_file( + get_config_resource_filepath("config_headers_child_only.yaml") + ) assert config.default_headers is None assert config.pod.default_headers is None assert config.key_manager.default_headers is None assert config.session_auth.default_headers is None - assert config.agent.default_headers == {"user-agent": "custom-user-agent", "header-key": "header-value"} + assert config.agent.default_headers == { + "user-agent": "custom-user-agent", + "header-key": "header-value", + } def test_load_default_headers_defined_at_global_and_child_level(): global_headers = {"user-agent": "global-user-agent", "header-key": "global-header-value"} - config = BdkConfigLoader.load_from_file(get_config_resource_filepath("config_headers_global_child.yaml")) + config = BdkConfigLoader.load_from_file( + get_config_resource_filepath("config_headers_global_child.yaml") + ) assert config.default_headers == global_headers assert config.pod.default_headers == global_headers assert config.key_manager.default_headers == global_headers assert config.session_auth.default_headers == global_headers - assert config.agent.default_headers == {"user-agent": "agent-user-agent", "header-key": "agent-header-value"} + assert config.agent.default_headers == { + "user-agent": "agent-user-agent", + "header-key": "agent-header-value", + } diff --git a/tests/core/config/bdk_config_parser_test.py b/tests/core/config/bdk_config_parser_test.py index 3de687ef..c8e470dc 100644 --- a/tests/core/config/bdk_config_parser_test.py +++ b/tests/core/config/bdk_config_parser_test.py @@ -2,7 +2,6 @@ from symphony.bdk.core.config.exception import BdkConfigError from symphony.bdk.core.config.loader import BdkConfigParser - from tests.utils.resource_utils import get_config_resource_filepath diff --git a/tests/core/config/bdk_config_test.py b/tests/core/config/bdk_config_test.py index a3aa619a..c1f6ccdb 100644 --- a/tests/core/config/bdk_config_test.py +++ b/tests/core/config/bdk_config_test.py @@ -43,15 +43,21 @@ def test_default_retry_configuration(): config = BdkConfigLoader.load_from_file(config_path) assert config.retry.max_attempts == BdkRetryConfig.DEFAULT_MAX_ATTEMPTS - assert config.retry.initial_interval == timedelta(milliseconds=BdkRetryConfig.DEFAULT_INITIAL_INTERVAL) + assert config.retry.initial_interval == timedelta( + milliseconds=BdkRetryConfig.DEFAULT_INITIAL_INTERVAL + ) assert config.retry.multiplier == BdkRetryConfig.DEFAULT_MULTIPLIER assert config.retry.max_interval == timedelta(milliseconds=BdkRetryConfig.DEFAULT_MAX_INTERVAL) # Datafeed default retry assert config.datafeed.retry.max_attempts == sys.maxsize - assert config.datafeed.retry.initial_interval == timedelta(milliseconds=BdkRetryConfig.DEFAULT_INITIAL_INTERVAL) + assert config.datafeed.retry.initial_interval == timedelta( + milliseconds=BdkRetryConfig.DEFAULT_INITIAL_INTERVAL + ) assert config.datafeed.retry.multiplier == BdkRetryConfig.DEFAULT_MULTIPLIER - assert config.datafeed.retry.max_interval == timedelta(milliseconds=BdkRetryConfig.DEFAULT_MAX_INTERVAL) + assert config.datafeed.retry.max_interval == timedelta( + milliseconds=BdkRetryConfig.DEFAULT_MAX_INTERVAL + ) def test_datafeed_retry_configuration(): diff --git a/tests/core/config/models/bdk_authentication_config_test.py b/tests/core/config/models/bdk_authentication_config_test.py index 25b68620..41c528b0 100644 --- a/tests/core/config/models/bdk_authentication_config_test.py +++ b/tests/core/config/models/bdk_authentication_config_test.py @@ -3,15 +3,18 @@ def test_rsa_configuration(): rsa_configuration = {"privateKey": {"path": "/path/to/bot/rsa-private-key.pem"}} - authentication_config = BdkAuthenticationConfig(private_key_config=rsa_configuration.get("privateKey")) + authentication_config = BdkAuthenticationConfig( + private_key_config=rsa_configuration.get("privateKey") + ) assert authentication_config.is_rsa_authentication_configured() is True assert authentication_config.is_rsa_authentication_configured() is True # Set up a non_empty content authentication_config.private_key.content = "not_empty" assert authentication_config.is_rsa_authentication_configured() is True - assert authentication_config.private_key._path is None, "rsa key path should have been overridden to None when " \ - "setting non empty content " + assert authentication_config.private_key._path is None, ( + "rsa key path should have been overridden to None when setting non empty content " + ) assert authentication_config.is_rsa_configuration_valid() is True # No content nor path @@ -23,6 +26,8 @@ def test_rsa_configuration(): def test_certificate_configuration(): certificate_path = "/path/to/bot-certificate.pem" certificate_configuration = {"certificate": {"path": certificate_path}} - authentication_config = BdkAuthenticationConfig(certificate_config=certificate_configuration.get("certificate")) + authentication_config = BdkAuthenticationConfig( + certificate_config=certificate_configuration.get("certificate") + ) assert authentication_config.is_certificate_configuration_valid() is True assert authentication_config.certificate.path == certificate_path diff --git a/tests/core/config/models/bdk_client_config_test.py b/tests/core/config/models/bdk_client_config_test.py index 31f8652c..ac5d4926 100644 --- a/tests/core/config/models/bdk_client_config_test.py +++ b/tests/core/config/models/bdk_client_config_test.py @@ -1,12 +1,17 @@ import pytest -from symphony.bdk.core.config.model.bdk_server_config import BdkServerConfig from symphony.bdk.core.config.model.bdk_client_config import BdkClientConfig +from symphony.bdk.core.config.model.bdk_server_config import BdkServerConfig @pytest.fixture(name="parent_config", scope="module") def fixture_parent_config(): - parent_config_dict = {"scheme": "parent_scheme", "host": "parent_host", "port": 0000, "context": "parent_context"} + parent_config_dict = { + "scheme": "parent_scheme", + "host": "parent_host", + "port": 0000, + "context": "parent_context", + } return BdkServerConfig(**parent_config_dict) @@ -23,7 +28,12 @@ def test_empty_client(parent_config): def test_full_client(parent_config): """Should get the client_config attributes from the client config when defined""" - client_config_dict = {"scheme": "client_scheme", "host": "client_host", "port": 1111, "context": "client_context"} + client_config_dict = { + "scheme": "client_scheme", + "host": "client_host", + "port": 1111, + "context": "client_context", + } client_config = BdkClientConfig(parent_config, client_config_dict) assert client_config.scheme == "client_scheme" diff --git a/tests/core/config/models/bdk_datafeed_config_test.py b/tests/core/config/models/bdk_datafeed_config_test.py index d55aae71..878953b0 100644 --- a/tests/core/config/models/bdk_datafeed_config_test.py +++ b/tests/core/config/models/bdk_datafeed_config_test.py @@ -2,7 +2,7 @@ import pytest -from symphony.bdk.core.config.model.bdk_datafeed_config import BdkDatafeedConfig, DF_V1 +from symphony.bdk.core.config.model.bdk_datafeed_config import DF_V1, BdkDatafeedConfig @pytest.fixture(name="datafeed_version", params=["v1"]) diff --git a/tests/core/config/models/bdk_datahose_config_test.py b/tests/core/config/models/bdk_datahose_config_test.py index 72935754..dfa46b0a 100644 --- a/tests/core/config/models/bdk_datahose_config_test.py +++ b/tests/core/config/models/bdk_datahose_config_test.py @@ -14,7 +14,12 @@ def test_empty_datahose_config(): def test_datahose_config_with_retry(): config_with_tag = {"tag": "TAG", "eventTypes": ["SOCIALMESSAGE", "CREATE_ROOM"]} - config_retry = {"maxAttempts": 10, "multiplier": 1.51, "maxIntervalMillis": 10000, "initialIntervalMillis": 2000} + config_retry = { + "maxAttempts": 10, + "multiplier": 1.51, + "maxIntervalMillis": 10000, + "initialIntervalMillis": 2000, + } config_with_tag["retry"] = config_retry datahose_config = BdkDatahoseConfig(config_with_tag) diff --git a/tests/core/config/models/bdk_server_config_test.py b/tests/core/config/models/bdk_server_config_test.py index cf27f001..37866a9e 100644 --- a/tests/core/config/models/bdk_server_config_test.py +++ b/tests/core/config/models/bdk_server_config_test.py @@ -1,4 +1,4 @@ -from symphony.bdk.core.config.model.bdk_server_config import BdkServerConfig, BdkProxyConfig +from symphony.bdk.core.config.model.bdk_server_config import BdkProxyConfig, BdkServerConfig def test_get_base_path(): diff --git a/tests/core/retry/__init__.py b/tests/core/retry/__init__.py index 7c501cde..f67daf05 100644 --- a/tests/core/retry/__init__.py +++ b/tests/core/retry/__init__.py @@ -1,5 +1,4 @@ from symphony.bdk.core.retry import AsyncRetrying - from symphony.bdk.gen import ApiException diff --git a/tests/core/retry/asyncio_test.py b/tests/core/retry/asyncio_test.py index be86ee9d..b92baf9b 100644 --- a/tests/core/retry/asyncio_test.py +++ b/tests/core/retry/asyncio_test.py @@ -5,9 +5,8 @@ from symphony.bdk.core.retry import retry from symphony.bdk.gen import ApiException - from tests.core.config import minimal_retry_config_with_attempts -from tests.core.retry import NoIOErrorAfterCount, NoApiExceptionAfterCount, retry_test_decorator +from tests.core.retry import NoApiExceptionAfterCount, NoIOErrorAfterCount, retry_test_decorator @retry_test_decorator @@ -69,6 +68,7 @@ async def async_return_text(retry_state): class TestDecoratorWrapper: """Testing the default decorator configuration taken from the class attribute _retry_config""" + _retry_config = minimal_retry_config_with_attempts(10) @retry diff --git a/tests/core/retry/strategy_test.py b/tests/core/retry/strategy_test.py index d739e8f9..d2bc5449 100644 --- a/tests/core/retry/strategy_test.py +++ b/tests/core/retry/strategy_test.py @@ -1,18 +1,20 @@ import asyncio +from unittest.mock import AsyncMock, Mock + import aiohttp import pytest -import symphony.bdk.core.retry.strategy as strategy -from unittest.mock import Mock, AsyncMock +import symphony.bdk.core.retry.strategy as strategy from symphony.bdk.core.auth.exception import AuthUnauthorizedError from symphony.bdk.core.retry import retry from symphony.bdk.gen import ApiException from tests.core.config import minimal_retry_config_with_attempts -from tests.core.retry import NoApiExceptionAfterCount, FixedChainedExceptions +from tests.core.retry import FixedChainedExceptions, NoApiExceptionAfterCount class TestAuthenticationStrategy: """Testing authentication_retry strategy""" + _retry_config = minimal_retry_config_with_attempts(1) @retry(retry=strategy.authentication_retry) @@ -39,6 +41,7 @@ async def test_unexpected_api_exception_is_raised(self): class TestRefreshSessionStrategy: """Testing refresh_session_if_unauthorized strategy""" + _retry_config = minimal_retry_config_with_attempts(2) @retry(retry=strategy.refresh_session_if_unauthorized) @@ -119,7 +122,7 @@ async def test_client_error_datahose_not_recreated_and_exception_rethrown(self): self.recreate_datahose.assert_not_called() @pytest.mark.asyncio - @pytest.mark.parametrize('status', [401, 429, 500]) + @pytest.mark.parametrize("status", [401, 429, 500]) async def test_unauthorized_error_refreshes_session_and_tries_again(self, status): self._retry_config = minimal_retry_config_with_attempts(2) self._auth_session = Mock() @@ -143,13 +146,18 @@ async def test_unexpected_api_exception_is_raised(self): @pytest.mark.asyncio async def test_should_retry(): - strategies = [TestAuthenticationStrategy(), TestRefreshSessionStrategy(), TestReadDatafeedStrategy()] + strategies = [ + TestAuthenticationStrategy(), + TestRefreshSessionStrategy(), + TestReadDatafeedStrategy(), + ] connection_key = Mock() connection_key.ssl = "ssl" exception_from_client = aiohttp.ClientConnectionError exception_from_a_timeout = asyncio.TimeoutError() - thing = FixedChainedExceptions([ApiException(429), ApiException(500), - exception_from_client, exception_from_a_timeout]) + thing = FixedChainedExceptions( + [ApiException(429), ApiException(500), exception_from_client, exception_from_a_timeout] + ) for s in strategies: s._retry_config = minimal_retry_config_with_attempts(5) diff --git a/tests/core/service/application/application_service_test.py b/tests/core/service/application/application_service_test.py index b47f2bab..a8db3d39 100644 --- a/tests/core/service/application/application_service_test.py +++ b/tests/core/service/application/application_service_test.py @@ -1,4 +1,4 @@ -from unittest.mock import MagicMock, AsyncMock +from unittest.mock import AsyncMock, MagicMock import pytest @@ -37,21 +37,23 @@ def fixture_app_entitlement_api(): @pytest.fixture(name="application_service") def fixture_application_service(application_api, app_entitlement_api, auth_session): - service = ApplicationService(application_api, app_entitlement_api, auth_session, minimal_retry_config()) + service = ApplicationService( + application_api, app_entitlement_api, auth_session, minimal_retry_config() + ) return service @pytest.mark.asyncio async def test_create_application(application_api, application_service): application_api.v1_admin_app_create_post = AsyncMock() - application_api.v1_admin_app_create_post.return_value = \ - get_deserialized_object_from_resource(ApplicationDetail, "application/create_application.json") + application_api.v1_admin_app_create_post.return_value = get_deserialized_object_from_resource( + ApplicationDetail, "application/create_application.json" + ) application_detail = await application_service.create_application(ApplicationDetail()) application_api.v1_admin_app_create_post.assert_called_with( - session_token="session_token", - application_detail=ApplicationDetail() + session_token="session_token", application_detail=ApplicationDetail() ) assert application_detail.application_info.app_id == "my-test-app" @@ -61,15 +63,18 @@ async def test_create_application(application_api, application_service): @pytest.mark.asyncio async def test_update_application(application_api, application_service): application_api.v1_admin_app_id_update_post = AsyncMock() - application_api.v1_admin_app_id_update_post.return_value = \ - get_deserialized_object_from_resource(ApplicationDetail, "application/update_application.json") + application_api.v1_admin_app_id_update_post.return_value = ( + get_deserialized_object_from_resource( + ApplicationDetail, "application/update_application.json" + ) + ) - application_detail = await application_service.update_application("my-test-app", ApplicationDetail()) + application_detail = await application_service.update_application( + "my-test-app", ApplicationDetail() + ) application_api.v1_admin_app_id_update_post.assert_called_with( - session_token="session_token", - id="my-test-app", - application_detail=ApplicationDetail() + session_token="session_token", id="my-test-app", application_detail=ApplicationDetail() ) assert application_detail.application_info.app_id == "my-test-app" @@ -83,22 +88,21 @@ async def test_delete_application(application_api, application_service): await application_service.delete_application("my-test-app") application_api.v1_admin_app_id_delete_post.assert_called_with( - session_token="session_token", - id="my-test-app" + session_token="session_token", id="my-test-app" ) @pytest.mark.asyncio async def test_get_application(application_api, application_service): application_api.v1_admin_app_id_get_get = AsyncMock() - application_api.v1_admin_app_id_get_get.return_value = \ - get_deserialized_object_from_resource(ApplicationDetail, "application/get_application.json") + application_api.v1_admin_app_id_get_get.return_value = get_deserialized_object_from_resource( + ApplicationDetail, "application/get_application.json" + ) application_detail = await application_service.get_application("my-test-app") application_api.v1_admin_app_id_get_get.assert_called_with( - session_token="session_token", - id="my-test-app" + session_token="session_token", id="my-test-app" ) assert application_detail.application_info.app_id == "my-test-app" @@ -108,8 +112,11 @@ async def test_get_application(application_api, application_service): @pytest.mark.asyncio async def test_list_application_entitlements(app_entitlement_api, application_service): app_entitlement_api.v1_admin_app_entitlement_list_get = AsyncMock() - app_entitlement_api.v1_admin_app_entitlement_list_get.return_value = \ - get_deserialized_object_from_resource(PodAppEntitlementList, "application/list_app_entitlements.json") + app_entitlement_api.v1_admin_app_entitlement_list_get.return_value = ( + get_deserialized_object_from_resource( + PodAppEntitlementList, "application/list_app_entitlements.json" + ) + ) pod_app_entitlements = await application_service.list_application_entitlements() @@ -124,21 +131,25 @@ async def test_list_application_entitlements(app_entitlement_api, application_se @pytest.mark.asyncio async def test_update_application_entitlements(app_entitlement_api, application_service): app_entitlement_api.v1_admin_app_entitlement_list_post = AsyncMock() - app_entitlement_api.v1_admin_app_entitlement_list_post.return_value = \ - get_deserialized_object_from_resource(PodAppEntitlementList, "application/update_app_entitlements.json") + app_entitlement_api.v1_admin_app_entitlement_list_post.return_value = ( + get_deserialized_object_from_resource( + PodAppEntitlementList, "application/update_app_entitlements.json" + ) + ) pod_app_entitlement = PodAppEntitlement( app_id="rsa-app-auth-example", app_name="App Auth RSA Example", enable=True, listed=True, - install=False + install=False, + ) + pod_app_entitlements = await application_service.update_application_entitlements( + [pod_app_entitlement] ) - pod_app_entitlements = await application_service.update_application_entitlements([pod_app_entitlement]) app_entitlement_api.v1_admin_app_entitlement_list_post.assert_called_with( - session_token="session_token", - payload=PodAppEntitlementList(value=[pod_app_entitlement]) + session_token="session_token", payload=PodAppEntitlementList(value=[pod_app_entitlement]) ) assert len(pod_app_entitlements) == 1 @@ -148,14 +159,16 @@ async def test_update_application_entitlements(app_entitlement_api, application_ @pytest.mark.asyncio async def test_list_user_applications(app_entitlement_api, application_service): app_entitlement_api.v1_admin_user_uid_app_entitlement_list_get = AsyncMock() - app_entitlement_api.v1_admin_user_uid_app_entitlement_list_get.return_value = \ - get_deserialized_object_from_resource(UserAppEntitlementList, "application/list_user_apps.json") + app_entitlement_api.v1_admin_user_uid_app_entitlement_list_get.return_value = ( + get_deserialized_object_from_resource( + UserAppEntitlementList, "application/list_user_apps.json" + ) + ) user_app_entitlements = await application_service.list_user_applications(1234) app_entitlement_api.v1_admin_user_uid_app_entitlement_list_get.assert_called_with( - session_token="session_token", - uid=1234 + session_token="session_token", uid=1234 ) assert len(user_app_entitlements) == 3 @@ -165,44 +178,49 @@ async def test_list_user_applications(app_entitlement_api, application_service): @pytest.mark.asyncio async def test_update_user_applications(app_entitlement_api, application_service): app_entitlement_api.v1_admin_user_uid_app_entitlement_list_post = AsyncMock() - app_entitlement_api.v1_admin_user_uid_app_entitlement_list_post.return_value = \ - get_deserialized_object_from_resource(UserAppEntitlementList, "application/list_user_apps.json") - - user_app_entitlement = UserAppEntitlement( - app_id="djApp", - listed=True, - install=False + app_entitlement_api.v1_admin_user_uid_app_entitlement_list_post.return_value = ( + get_deserialized_object_from_resource( + UserAppEntitlementList, "application/list_user_apps.json" + ) ) - user_app_entitlements = await application_service.update_user_applications(1234, [user_app_entitlement]) + user_app_entitlement = UserAppEntitlement(app_id="djApp", listed=True, install=False) + + user_app_entitlements = await application_service.update_user_applications( + 1234, [user_app_entitlement] + ) app_entitlement_api.v1_admin_user_uid_app_entitlement_list_post.assert_called_with( session_token="session_token", uid=1234, - payload=UserAppEntitlementList(value=[user_app_entitlement]) + payload=UserAppEntitlementList(value=[user_app_entitlement]), ) assert len(user_app_entitlements) == 3 assert user_app_entitlements[0].app_id == "djApp" + @pytest.mark.asyncio async def test_patch_user_applications(app_entitlement_api, application_service): app_entitlement_api.v1_admin_user_uid_app_entitlement_list_patch = AsyncMock() - app_entitlement_api.v1_admin_user_uid_app_entitlement_list_patch.return_value = \ - get_deserialized_object_from_resource(UserAppEntitlementList, "application/list_user_apps.json") + app_entitlement_api.v1_admin_user_uid_app_entitlement_list_patch.return_value = ( + get_deserialized_object_from_resource( + UserAppEntitlementList, "application/list_user_apps.json" + ) + ) user_app_entitlement_patch = UserAppEntitlementPatch( - app_id="djApp", - listed="KEEP", - install="KEEP" + app_id="djApp", listed="KEEP", install="KEEP" ) - user_app_patched_entitlements = await application_service.patch_user_applications(1234, [user_app_entitlement_patch]) + user_app_patched_entitlements = await application_service.patch_user_applications( + 1234, [user_app_entitlement_patch] + ) app_entitlement_api.v1_admin_user_uid_app_entitlement_list_patch.assert_called_with( session_token="session_token", uid=1234, - payload=UserAppEntitlementsPatchList(value=[user_app_entitlement_patch]) + payload=UserAppEntitlementsPatchList(value=[user_app_entitlement_patch]), ) assert len(user_app_patched_entitlements) == 3 diff --git a/tests/core/service/connection/connection_service_test.py b/tests/core/service/connection/connection_service_test.py index 2932756f..b39f6718 100644 --- a/tests/core/service/connection/connection_service_test.py +++ b/tests/core/service/connection/connection_service_test.py @@ -1,4 +1,4 @@ -from unittest.mock import MagicMock, AsyncMock +from unittest.mock import AsyncMock, MagicMock import pytest @@ -28,28 +28,21 @@ def fixture_connection_api(): @pytest.fixture(name="connection_service") def fixture_connection_service(connection_api, auth_session): - service = ConnectionService( - connection_api, - auth_session, - minimal_retry_config() - ) + service = ConnectionService(connection_api, auth_session, minimal_retry_config()) return service @pytest.mark.asyncio async def test_get_connection(connection_api, connection_service): connection_api.v1_connection_user_user_id_info_get = AsyncMock() - connection_api.v1_connection_user_user_id_info_get.return_value = \ - deserialize_object(UserConnection, "{" - " \"userId\": 769658112378," - " \"status\": \"ACCEPTED\"" - "}") + connection_api.v1_connection_user_user_id_info_get.return_value = deserialize_object( + UserConnection, '{ "userId": 769658112378, "status": "ACCEPTED"}' + ) user_connection = await connection_service.get_connection(769658112378) connection_api.v1_connection_user_user_id_info_get.assert_called_with( - user_id="769658112378", - session_token="session_token" + user_id="769658112378", session_token="session_token" ) assert user_connection.user_id == 769658112378 assert user_connection.status == ConnectionStatus.ACCEPTED.value @@ -58,26 +51,30 @@ async def test_get_connection(connection_api, connection_service): @pytest.mark.asyncio async def test_list_connections(connection_api, connection_service): connection_api.v1_connection_list_get = AsyncMock() - connection_api.v1_connection_list_get.return_value = deserialize_object(UserConnectionList, - "[" - " {" - " \"userId\": 7078106126503," - " \"status\": \"PENDING_OUTGOING\"," - " \"updatedAt\": 1471018076255" - " }," - " {" - " \"userId\": 7078106103809," - " \"status\": \"PENDING_INCOMING\"," - " \"updatedAt\": 1467562406219" - " }" - "]") - - user_connections = await connection_service.list_connections(ConnectionStatus.ALL, [7078106126503, 7078106103809]) + connection_api.v1_connection_list_get.return_value = deserialize_object( + UserConnectionList, + "[" + " {" + ' "userId": 7078106126503,' + ' "status": "PENDING_OUTGOING",' + ' "updatedAt": 1471018076255' + " }," + " {" + ' "userId": 7078106103809,' + ' "status": "PENDING_INCOMING",' + ' "updatedAt": 1467562406219' + " }" + "]", + ) + + user_connections = await connection_service.list_connections( + ConnectionStatus.ALL, [7078106126503, 7078106103809] + ) connection_api.v1_connection_list_get.assert_called_with( user_ids="7078106126503,7078106103809", status=ConnectionStatus.ALL.value, - session_token="session_token" + session_token="session_token", ) assert len(user_connections) == 2 @@ -88,20 +85,22 @@ async def test_list_connections(connection_api, connection_service): @pytest.mark.asyncio async def test_create_connection(connection_api, connection_service): connection_api.v1_connection_create_post = AsyncMock() - connection_api.v1_connection_create_post.return_value = deserialize_object(UserConnection, - "{" - " \"userId\": 7078106126503," - " \"status\": \"PENDING_OUTGOING\"," - " \"firstRequestedAt\": 1471018076255," - " \"updatedAt\": 1471018076255," - " \"requestCounter\": 1" - "}") + connection_api.v1_connection_create_post.return_value = deserialize_object( + UserConnection, + "{" + ' "userId": 7078106126503,' + ' "status": "PENDING_OUTGOING",' + ' "firstRequestedAt": 1471018076255,' + ' "updatedAt": 1471018076255,' + ' "requestCounter": 1' + "}", + ) user_connection = await connection_service.create_connection(7078106126503) connection_api.v1_connection_create_post.assert_called_with( connection_request=UserConnectionRequest(user_id=7078106126503), - session_token="session_token" + session_token="session_token", ) assert user_connection.user_id == 7078106126503 @@ -112,20 +111,22 @@ async def test_create_connection(connection_api, connection_service): @pytest.mark.asyncio async def test_accept_connection(connection_api, connection_service): connection_api.v1_connection_accept_post = AsyncMock() - connection_api.v1_connection_accept_post.return_value = deserialize_object(UserConnection, - "{" - " \"userId\": 7078106169577," - " \"status\": \"ACCEPTED\"," - " \"firstRequestedAt\": 1471046357339," - " \"updatedAt\": 1471046517684," - " \"requestCounter\": 1" - "}") + connection_api.v1_connection_accept_post.return_value = deserialize_object( + UserConnection, + "{" + ' "userId": 7078106169577,' + ' "status": "ACCEPTED",' + ' "firstRequestedAt": 1471046357339,' + ' "updatedAt": 1471046517684,' + ' "requestCounter": 1' + "}", + ) user_connection = await connection_service.accept_connection(7078106169577) connection_api.v1_connection_accept_post.assert_called_with( connection_request=UserConnectionRequest(user_id=7078106169577), - session_token="session_token" + session_token="session_token", ) assert user_connection.user_id == 7078106169577 @@ -136,20 +137,22 @@ async def test_accept_connection(connection_api, connection_service): @pytest.mark.asyncio async def test_reject_connection(connection_api, connection_service): connection_api.v1_connection_reject_post = AsyncMock() - connection_api.v1_connection_reject_post.return_value = deserialize_object(UserConnection, - "{" - " \"userId\": 7215545059385," - " \"status\": \"REJECTED\"," - " \"firstRequestedAt\": 1471044955409," - " \"updatedAt\": 1471045390420," - " \"requestCounter\": 1" - "}") + connection_api.v1_connection_reject_post.return_value = deserialize_object( + UserConnection, + "{" + ' "userId": 7215545059385,' + ' "status": "REJECTED",' + ' "firstRequestedAt": 1471044955409,' + ' "updatedAt": 1471045390420,' + ' "requestCounter": 1' + "}", + ) user_connection = await connection_service.reject_connection(7215545059385) connection_api.v1_connection_reject_post.assert_called_with( connection_request=UserConnectionRequest(user_id=7215545059385), - session_token="session_token" + session_token="session_token", ) assert user_connection.user_id == 7215545059385 @@ -163,6 +166,5 @@ async def test_remove_connection(connection_api, connection_service): await connection_service.remove_connection(1234) connection_api.v1_connection_user_uid_remove_post.assert_called_with( - uid=1234, - session_token="session_token" + uid=1234, session_token="session_token" ) diff --git a/tests/core/service/datafeed/abstract_ackId_event_loop_test.py b/tests/core/service/datafeed/abstract_ackId_event_loop_test.py index f98c2b72..520262b0 100644 --- a/tests/core/service/datafeed/abstract_ackId_event_loop_test.py +++ b/tests/core/service/datafeed/abstract_ackId_event_loop_test.py @@ -1,14 +1,20 @@ +# ruff: noqa import asyncio -import pytest - from unittest.mock import AsyncMock, patch -from tests.core.service.datafeed.test_fixtures import fixture_initiator_userid, fixture_session_service, \ - fixture_message_sent_v4_event +from tests.core.service.datafeed.test_fixtures import ( + fixture_initiator_userid, + fixture_session_service, + fixture_message_sent_v4_event, +) + +import pytest -from symphony.bdk.gen.agent_model.v5_event_list import V5EventList from symphony.bdk.core.config.model.bdk_config import BdkConfig -from symphony.bdk.core.service.datafeed.abstract_ackId_event_loop import AbstractAckIdEventLoop +from symphony.bdk.core.service.datafeed.abstract_ackId_event_loop import ( + AbstractAckIdEventLoop, +) from symphony.bdk.gen.agent_api.datafeed_api import DatafeedApi +from symphony.bdk.gen.agent_model.v5_event_list import V5EventList @pytest.fixture(name="read_events_side_effect") @@ -27,7 +33,9 @@ async def read_events(): def fixture_bare_df_loop(session_service): # patch.multiple called in order to be able to instantiate AbstractDatafeedLoop with patch.multiple(AbstractAckIdEventLoop, __abstractmethods__=set()): - mock_df = AbstractAckIdEventLoop(DatafeedApi(AsyncMock()), session_service, None, BdkConfig()) + mock_df = AbstractAckIdEventLoop( + DatafeedApi(AsyncMock()), session_service, None, BdkConfig() + ) mock_df._read_events = AsyncMock() mock_df._run_listener_tasks = AsyncMock() return mock_df diff --git a/tests/core/service/datafeed/abstract_datafeed_loop_test.py b/tests/core/service/datafeed/abstract_datafeed_loop_test.py index 1fd1b6d5..a6c86073 100644 --- a/tests/core/service/datafeed/abstract_datafeed_loop_test.py +++ b/tests/core/service/datafeed/abstract_datafeed_loop_test.py @@ -1,36 +1,55 @@ +# ruff: noqa import asyncio -from unittest.mock import AsyncMock, patch, call +from unittest.mock import AsyncMock, call, patch import pytest -from tests.core.service.datafeed.test_fixtures import fixture_initiator_userid, fixture_session_service, \ - fixture_message_sent_v4_event +from tests.core.service.datafeed.test_fixtures import ( + fixture_initiator_userid, + fixture_session_service, + fixture_message_sent_v4_event, +) from symphony.bdk.core.config.model.bdk_config import BdkConfig -from symphony.bdk.core.service.datafeed.abstract_datafeed_loop import AbstractDatafeedLoop, RealTimeEvent -from symphony.bdk.core.service.datafeed.real_time_event_listener import RealTimeEventListener +from symphony.bdk.core.service.datafeed.abstract_datafeed_loop import ( + AbstractDatafeedLoop, + RealTimeEvent, +) +from symphony.bdk.core.service.datafeed.real_time_event_listener import ( + RealTimeEventListener, +) from symphony.bdk.gen.agent_api.datafeed_api import DatafeedApi from symphony.bdk.gen.agent_model.v4_connection_accepted import V4ConnectionAccepted from symphony.bdk.gen.agent_model.v4_connection_requested import V4ConnectionRequested from symphony.bdk.gen.agent_model.v4_event import V4Event from symphony.bdk.gen.agent_model.v4_initiator import V4Initiator -from symphony.bdk.gen.agent_model.v4_instant_message_created import V4InstantMessageCreated +from symphony.bdk.gen.agent_model.v4_instant_message_created import ( + V4InstantMessageCreated, +) from symphony.bdk.gen.agent_model.v4_message import V4Message from symphony.bdk.gen.agent_model.v4_message_sent import V4MessageSent from symphony.bdk.gen.agent_model.v4_message_suppressed import V4MessageSuppressed from symphony.bdk.gen.agent_model.v4_payload import V4Payload from symphony.bdk.gen.agent_model.v4_room_created import V4RoomCreated from symphony.bdk.gen.agent_model.v4_room_deactivated import V4RoomDeactivated -from symphony.bdk.gen.agent_model.v4_room_member_demoted_from_owner import V4RoomMemberDemotedFromOwner -from symphony.bdk.gen.agent_model.v4_room_member_promoted_to_owner import V4RoomMemberPromotedToOwner +from symphony.bdk.gen.agent_model.v4_room_member_demoted_from_owner import ( + V4RoomMemberDemotedFromOwner, +) +from symphony.bdk.gen.agent_model.v4_room_member_promoted_to_owner import ( + V4RoomMemberPromotedToOwner, +) from symphony.bdk.gen.agent_model.v4_room_reactivated import V4RoomReactivated from symphony.bdk.gen.agent_model.v4_room_updated import V4RoomUpdated from symphony.bdk.gen.agent_model.v4_shared_post import V4SharedPost -from symphony.bdk.gen.agent_model.v4_symphony_elements_action import V4SymphonyElementsAction +from symphony.bdk.gen.agent_model.v4_symphony_elements_action import ( + V4SymphonyElementsAction, +) from symphony.bdk.gen.agent_model.v4_user import V4User from symphony.bdk.gen.agent_model.v4_user_joined_room import V4UserJoinedRoom from symphony.bdk.gen.agent_model.v4_user_left_room import V4UserLeftRoom -from symphony.bdk.gen.agent_model.v4_user_requested_to_join_room import V4UserRequestedToJoinRoom +from symphony.bdk.gen.agent_model.v4_user_requested_to_join_room import ( + V4UserRequestedToJoinRoom, +) from symphony.bdk.gen.pod_model.user_v2 import UserV2 BOT_USER_ID = 12345 @@ -45,8 +64,12 @@ async def create_and_await_tasks(datafeed_loop, events): @pytest.fixture(name="bot_message_sent_event") def fixture_bot_message_sent_event(): initiator = V4Initiator(user=V4User(user_id=BOT_USER_ID)) - payload = V4Payload(message_sent=V4MessageSent(message=V4Message(attachments=[], message="message"))) - return V4Event(type=RealTimeEvent.MESSAGESENT.name, payload=payload, initiator=initiator) + payload = V4Payload( + message_sent=V4MessageSent(message=V4Message(attachments=[], message="message")) + ) + return V4Event( + type=RealTimeEvent.MESSAGESENT.name, payload=payload, initiator=initiator + ) @pytest.fixture(name="listener") @@ -66,7 +89,9 @@ async def run_iteration(**kwargs): def fixture_bare_df_loop(session_service): # patch.multiple called in order to be able to instantiate AbstractDatafeedLoop with patch.multiple(AbstractDatafeedLoop, __abstractmethods__=set()): - mock_df = AbstractDatafeedLoop(DatafeedApi(AsyncMock()), session_service, None, BdkConfig()) + mock_df = AbstractDatafeedLoop( + DatafeedApi(AsyncMock()), session_service, None, BdkConfig() + ) mock_df._stop_listener_tasks = AsyncMock() mock_df._run_loop_iteration = AsyncMock() @@ -145,7 +170,9 @@ async def test_create_listener_tasks_list_with_none(df_loop, listener): @pytest.mark.asyncio -async def test_create_listener_tasks_list_with_element(df_loop, listener, message_sent_v4_event): +async def test_create_listener_tasks_list_with_element( + df_loop, listener, message_sent_v4_event +): tasks = await df_loop._create_listener_tasks([message_sent_v4_event]) assert len(tasks) == 1 @@ -153,7 +180,9 @@ async def test_create_listener_tasks_list_with_element(df_loop, listener, messag @pytest.mark.asyncio -async def test_create_listener_tasks_list_with_element_and_none(df_loop, listener, message_sent_v4_event): +async def test_create_listener_tasks_list_with_element_and_none( + df_loop, listener, message_sent_v4_event +): tasks = await df_loop._create_listener_tasks([message_sent_v4_event, None]) assert len(tasks) == 1 @@ -161,37 +190,55 @@ async def test_create_listener_tasks_list_with_element_and_none(df_loop, listene @pytest.mark.asyncio -async def test_create_listener_tasks_list_with_two_elements(df_loop, listener, message_sent_v4_event): - tasks = await df_loop._create_listener_tasks([message_sent_v4_event, message_sent_v4_event]) +async def test_create_listener_tasks_list_with_two_elements( + df_loop, listener, message_sent_v4_event +): + tasks = await df_loop._create_listener_tasks( + [message_sent_v4_event, message_sent_v4_event] + ) assert len(tasks) == 2 listener.is_accepting_event.assert_has_awaits( - [call(message_sent_v4_event, BOT_INFO), call(message_sent_v4_event, BOT_INFO)]) + [call(message_sent_v4_event, BOT_INFO), call(message_sent_v4_event, BOT_INFO)] + ) @pytest.mark.asyncio -async def test_create_listener_tasks_list_not_accepting_events(df_loop, listener, bot_message_sent_event): +async def test_create_listener_tasks_list_not_accepting_events( + df_loop, listener, bot_message_sent_event +): tasks = await df_loop._create_listener_tasks([bot_message_sent_event]) assert len(tasks) == 0 - listener.is_accepting_event.assert_called_once_with(bot_message_sent_event, BOT_INFO) + listener.is_accepting_event.assert_called_once_with( + bot_message_sent_event, BOT_INFO + ) @pytest.mark.asyncio -async def test_create_listener_tasks_list(df_loop, listener, message_sent_v4_event, bot_message_sent_event): - tasks = await df_loop._create_listener_tasks([message_sent_v4_event, bot_message_sent_event]) +async def test_create_listener_tasks_list( + df_loop, listener, message_sent_v4_event, bot_message_sent_event +): + tasks = await df_loop._create_listener_tasks( + [message_sent_v4_event, bot_message_sent_event] + ) assert len(tasks) == 1 listener.is_accepting_event.assert_has_awaits( - [call(message_sent_v4_event, BOT_INFO), call(bot_message_sent_event, BOT_INFO)]) + [call(message_sent_v4_event, BOT_INFO), call(bot_message_sent_event, BOT_INFO)] + ) @pytest.mark.asyncio -async def test_create_listener_tasks_list_several_listeners(df_loop, message_sent_v4_event, bot_message_sent_event): +async def test_create_listener_tasks_list_several_listeners( + df_loop, message_sent_v4_event, bot_message_sent_event +): df_loop.subscribe(RealTimeEventListener()) - tasks = await df_loop._create_listener_tasks([message_sent_v4_event, bot_message_sent_event]) + tasks = await df_loop._create_listener_tasks( + [message_sent_v4_event, bot_message_sent_event] + ) assert len(tasks) == 2 @@ -199,38 +246,54 @@ async def test_create_listener_tasks_list_several_listeners(df_loop, message_sen @pytest.mark.asyncio async def test_handle_message_sent(df_loop, listener, initiator_userid): payload = V4Payload(message_sent=V4MessageSent()) - event = V4Event(type=RealTimeEvent.MESSAGESENT.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.MESSAGESENT.name, payload=payload, initiator=initiator_userid + ) tasks = await df_loop._create_listener_tasks([event]) await asyncio.gather(*tasks) - listener.on_message_sent.assert_called_once_with(initiator_userid, payload.message_sent) + listener.on_message_sent.assert_called_once_with( + initiator_userid, payload.message_sent + ) @pytest.mark.asyncio async def test_handle_shared_post(df_loop, listener, initiator_userid): payload = V4Payload(shared_post=V4SharedPost()) - event = V4Event(type=RealTimeEvent.SHAREDPOST.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.SHAREDPOST.name, payload=payload, initiator=initiator_userid + ) await create_and_await_tasks(df_loop, [event]) - listener.on_shared_post.assert_called_once_with(initiator_userid, payload.shared_post) + listener.on_shared_post.assert_called_once_with( + initiator_userid, payload.shared_post + ) @pytest.mark.asyncio async def test_handle_instant_message_created(df_loop, listener, initiator_userid): payload = V4Payload(instant_message_created=V4InstantMessageCreated()) - event = V4Event(type=RealTimeEvent.INSTANTMESSAGECREATED.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.INSTANTMESSAGECREATED.name, + payload=payload, + initiator=initiator_userid, + ) await create_and_await_tasks(df_loop, [event]) - listener.on_instant_message_created.assert_called_with(initiator_userid, payload.instant_message_created) + listener.on_instant_message_created.assert_called_with( + initiator_userid, payload.instant_message_created + ) @pytest.mark.asyncio async def test_handle_room_created(df_loop, listener, initiator_userid): payload = V4Payload(room_created=V4RoomCreated()) - event = V4Event(type=RealTimeEvent.ROOMCREATED.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.ROOMCREATED.name, payload=payload, initiator=initiator_userid + ) await create_and_await_tasks(df_loop, [event]) @@ -240,7 +303,9 @@ async def test_handle_room_created(df_loop, listener, initiator_userid): @pytest.mark.asyncio async def test_handle_room_updated(df_loop, listener, initiator_userid): payload = V4Payload(room_updated=V4RoomUpdated()) - event = V4Event(type=RealTimeEvent.ROOMUPDATED.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.ROOMUPDATED.name, payload=payload, initiator=initiator_userid + ) await create_and_await_tasks(df_loop, [event]) @@ -250,113 +315,182 @@ async def test_handle_room_updated(df_loop, listener, initiator_userid): @pytest.mark.asyncio async def test_handle_room_deactivated(df_loop, listener, initiator_userid): payload = V4Payload(room_deactivated=V4RoomDeactivated()) - event = V4Event(type=RealTimeEvent.ROOMDEACTIVATED.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.ROOMDEACTIVATED.name, + payload=payload, + initiator=initiator_userid, + ) await create_and_await_tasks(df_loop, [event]) - listener.on_room_deactivated.assert_called_with(initiator_userid, payload.room_deactivated) + listener.on_room_deactivated.assert_called_with( + initiator_userid, payload.room_deactivated + ) @pytest.mark.asyncio async def test_handle_room_reactivated(df_loop, listener, initiator_userid): payload = V4Payload(room_reactivated=V4RoomReactivated()) - event = V4Event(type=RealTimeEvent.ROOMREACTIVATED.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.ROOMREACTIVATED.name, + payload=payload, + initiator=initiator_userid, + ) await create_and_await_tasks(df_loop, [event]) listener.is_accepting_event.assert_called_with(event, BOT_INFO) - listener.on_room_reactivated.assert_called_with(initiator_userid, payload.room_reactivated) + listener.on_room_reactivated.assert_called_with( + initiator_userid, payload.room_reactivated + ) @pytest.mark.asyncio async def test_handle_user_requested_to_join_room(df_loop, listener, initiator_userid): payload = V4Payload(user_requested_to_join_room=V4UserRequestedToJoinRoom()) - event = V4Event(type=RealTimeEvent.USERREQUESTEDTOJOINROOM.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.USERREQUESTEDTOJOINROOM.name, + payload=payload, + initiator=initiator_userid, + ) await create_and_await_tasks(df_loop, [event]) - listener.on_user_requested_to_join_room.assert_called_with(initiator_userid, payload.user_requested_to_join_room) + listener.on_user_requested_to_join_room.assert_called_with( + initiator_userid, payload.user_requested_to_join_room + ) @pytest.mark.asyncio async def test_handle_user_joined_room(df_loop, listener, initiator_userid): payload = V4Payload(user_joined_room=V4UserJoinedRoom()) - event = V4Event(type=RealTimeEvent.USERJOINEDROOM.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.USERJOINEDROOM.name, + payload=payload, + initiator=initiator_userid, + ) await create_and_await_tasks(df_loop, [event]) - listener.on_user_joined_room.assert_called_with(initiator_userid, payload.user_joined_room) + listener.on_user_joined_room.assert_called_with( + initiator_userid, payload.user_joined_room + ) @pytest.mark.asyncio async def test_handle_user_left_room(df_loop, listener, initiator_userid): payload = V4Payload(user_left_room=V4UserLeftRoom()) - event = V4Event(type=RealTimeEvent.USERLEFTROOM.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.USERLEFTROOM.name, + payload=payload, + initiator=initiator_userid, + ) await create_and_await_tasks(df_loop, [event]) - listener.on_user_left_room.assert_called_with(initiator_userid, payload.user_left_room) + listener.on_user_left_room.assert_called_with( + initiator_userid, payload.user_left_room + ) @pytest.mark.asyncio -async def test_handle_room_member_promoted_to_owner(df_loop, listener, initiator_userid): +async def test_handle_room_member_promoted_to_owner( + df_loop, listener, initiator_userid +): payload = V4Payload(room_member_promoted_to_owner=V4RoomMemberPromotedToOwner()) - event = V4Event(type=RealTimeEvent.ROOMMEMBERPROMOTEDTOOWNER.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.ROOMMEMBERPROMOTEDTOOWNER.name, + payload=payload, + initiator=initiator_userid, + ) await create_and_await_tasks(df_loop, [event]) - listener.on_room_member_promoted_to_owner.\ - assert_called_with(initiator_userid, payload.room_member_promoted_to_owner) + listener.on_room_member_promoted_to_owner.assert_called_with( + initiator_userid, payload.room_member_promoted_to_owner + ) @pytest.mark.asyncio -async def test_handle_room_member_demoted_from_owner(df_loop, listener, initiator_userid): +async def test_handle_room_member_demoted_from_owner( + df_loop, listener, initiator_userid +): payload = V4Payload(room_member_demoted_from_owner=V4RoomMemberDemotedFromOwner()) - event = V4Event(type=RealTimeEvent.ROOMMEMBERDEMOTEDFROMOWNER.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.ROOMMEMBERDEMOTEDFROMOWNER.name, + payload=payload, + initiator=initiator_userid, + ) await create_and_await_tasks(df_loop, [event]) - listener.on_room_demoted_from_owner.assert_called_with(initiator_userid, payload.room_member_demoted_from_owner) + listener.on_room_demoted_from_owner.assert_called_with( + initiator_userid, payload.room_member_demoted_from_owner + ) @pytest.mark.asyncio async def test_handle_connection_requested(df_loop, listener, initiator_userid): payload = V4Payload(connection_requested=V4ConnectionRequested()) - event = V4Event(type=RealTimeEvent.CONNECTIONREQUESTED.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.CONNECTIONREQUESTED.name, + payload=payload, + initiator=initiator_userid, + ) await create_and_await_tasks(df_loop, [event]) - listener.on_connection_requested.assert_called_with(initiator_userid, payload.connection_requested) + listener.on_connection_requested.assert_called_with( + initiator_userid, payload.connection_requested + ) @pytest.mark.asyncio async def test_handle_connection_accepted(df_loop, listener, initiator_userid): payload = V4Payload(connection_accepted=V4ConnectionAccepted()) - event = V4Event(type=RealTimeEvent.CONNECTIONACCEPTED.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.CONNECTIONACCEPTED.name, + payload=payload, + initiator=initiator_userid, + ) await create_and_await_tasks(df_loop, [event]) - listener.on_connection_accepted.assert_called_with(initiator_userid, payload.connection_accepted) + listener.on_connection_accepted.assert_called_with( + initiator_userid, payload.connection_accepted + ) @pytest.mark.asyncio async def test_handle_message_suppressed(df_loop, listener, initiator_userid): payload = V4Payload(message_suppressed=V4MessageSuppressed()) - event = V4Event(type=RealTimeEvent.MESSAGESUPPRESSED.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.MESSAGESUPPRESSED.name, + payload=payload, + initiator=initiator_userid, + ) await create_and_await_tasks(df_loop, [event]) - listener.on_message_suppressed.assert_called_with(initiator_userid, payload.message_suppressed) + listener.on_message_suppressed.assert_called_with( + initiator_userid, payload.message_suppressed + ) @pytest.mark.asyncio async def test_handle_symphony_element(df_loop, listener, initiator_userid): payload = V4Payload(symphony_elements_action=V4SymphonyElementsAction()) - event = V4Event(type=RealTimeEvent.SYMPHONYELEMENTSACTION.name, payload=payload, initiator=initiator_userid) + event = V4Event( + type=RealTimeEvent.SYMPHONYELEMENTSACTION.name, + payload=payload, + initiator=initiator_userid, + ) await create_and_await_tasks(df_loop, [event]) - listener.on_symphony_elements_action.assert_called_once_with(initiator_userid, payload.symphony_elements_action) + listener.on_symphony_elements_action.assert_called_once_with( + initiator_userid, payload.symphony_elements_action + ) @pytest.mark.asyncio diff --git a/tests/core/service/datafeed/datafeed_loop_v1_test.py b/tests/core/service/datafeed/datafeed_loop_v1_test.py index 8547b7d1..07678f55 100644 --- a/tests/core/service/datafeed/datafeed_loop_v1_test.py +++ b/tests/core/service/datafeed/datafeed_loop_v1_test.py @@ -1,17 +1,23 @@ +# ruff: noqa import asyncio -from unittest.mock import MagicMock, AsyncMock, call +from unittest.mock import AsyncMock, MagicMock, call import pytest -from tests.core.service.datafeed.test_fixtures import fixture_initiator_username, fixture_session_service, \ - fixture_auth_session +from tests.core.service.datafeed.test_fixtures import ( + fixture_initiator_username, + fixture_session_service, + fixture_auth_session, +) from symphony.bdk.core.config.loader import BdkConfigLoader from symphony.bdk.core.config.model.bdk_datafeed_config import BdkDatafeedConfig from symphony.bdk.core.service.datafeed.abstract_datafeed_loop import RealTimeEvent from symphony.bdk.core.service.datafeed.datafeed_loop_v1 import DatafeedLoopV1 from symphony.bdk.core.service.datafeed.exception import EventError -from symphony.bdk.core.service.datafeed.real_time_event_listener import RealTimeEventListener +from symphony.bdk.core.service.datafeed.real_time_event_listener import ( + RealTimeEventListener, +) from symphony.bdk.gen.agent_api.datafeed_api import DatafeedApi from symphony.bdk.gen.agent_model.datafeed import Datafeed from symphony.bdk.gen.agent_model.v4_event import V4Event @@ -21,9 +27,13 @@ from symphony.bdk.gen.api_client import ApiClient from symphony.bdk.gen.exceptions import ApiException from tests.core.config import minimal_retry_config_with_attempts -from tests.core.test.in_memory_datafeed_id_repository import InMemoryDatafeedIdRepository -from tests.utils.resource_utils import get_config_resource_filepath -from tests.utils.resource_utils import get_resource_content +from tests.core.test.in_memory_datafeed_id_repository import ( + InMemoryDatafeedIdRepository, +) +from tests.utils.resource_utils import ( + get_config_resource_filepath, + get_resource_content, +) SLEEP_SECONDS = 0.0001 @@ -54,9 +64,11 @@ def fixture_datafeed_api(): @pytest.fixture(name="message_sent") def fixture_message_sent(initiator_username): - return V4Event(type=RealTimeEvent.MESSAGESENT.name, - payload=V4Payload(message_sent=V4MessageSent()), - initiator=initiator_username) + return V4Event( + type=RealTimeEvent.MESSAGESENT.name, + payload=V4Payload(message_sent=V4MessageSent()), + initiator=initiator_username, + ) @pytest.fixture(name="message_sent_event") @@ -83,16 +95,23 @@ async def read_df(**kwargs): @pytest.fixture(name="datafeed_loop_v1") -def fixture_datafeed_loop_v1(datafeed_api, session_service, auth_session, config, datafeed_repository): - df_loop = auto_stopping_datafeed_loop_v1(datafeed_api, session_service, auth_session, config, datafeed_repository) +def fixture_datafeed_loop_v1( + datafeed_api, session_service, auth_session, config, datafeed_repository +): + df_loop = auto_stopping_datafeed_loop_v1( + datafeed_api, session_service, auth_session, config, datafeed_repository + ) df_loop._retry_config = minimal_retry_config_with_attempts(1) return df_loop @pytest.fixture(name="mock_datafeed_loop_v1") -def fixture_mock_datafeed_loop_v1_(datafeed_api, session_service, config, datafeed_repository, - read_df_loop_side_effect): - datafeed_loop = DatafeedLoopV1(datafeed_api, session_service, None, config, repository=datafeed_repository) +def fixture_mock_datafeed_loop_v1_( + datafeed_api, session_service, config, datafeed_repository, read_df_loop_side_effect +): + datafeed_loop = DatafeedLoopV1( + datafeed_api, session_service, None, config, repository=datafeed_repository + ) datafeed_loop._prepare_datafeed = AsyncMock() datafeed_loop.recreate_datafeed = AsyncMock() datafeed_loop._read_datafeed = AsyncMock(side_effect=read_df_loop_side_effect) @@ -100,11 +119,14 @@ def fixture_mock_datafeed_loop_v1_(datafeed_api, session_service, config, datafe return datafeed_loop -def auto_stopping_datafeed_loop_v1(datafeed_api, session_service, auth_session, config, repository=None): - datafeed_loop = DatafeedLoopV1(datafeed_api, session_service, auth_session, config, repository) +def auto_stopping_datafeed_loop_v1( + datafeed_api, session_service, auth_session, config, repository=None +): + datafeed_loop = DatafeedLoopV1( + datafeed_api, session_service, auth_session, config, repository + ) class RealTimeEventListenerImpl(RealTimeEventListener): - async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): await datafeed_loop.stop() @@ -113,7 +135,9 @@ async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): @pytest.mark.asyncio -async def test_start(datafeed_loop_v1, datafeed_api, session_service, read_df_side_effect): +async def test_start( + datafeed_loop_v1, datafeed_api, session_service, read_df_side_effect +): datafeed_api.v4_datafeed_create_post.return_value = Datafeed(id="test_id") datafeed_api.v4_datafeed_id_read_get.side_effect = read_df_side_effect @@ -128,9 +152,13 @@ async def test_start(datafeed_loop_v1, datafeed_api, session_service, read_df_si @pytest.mark.asyncio -async def test_start_already_started_datafeed_v1_loop_should_throw_error(datafeed_loop_v1): +async def test_start_already_started_datafeed_v1_loop_should_throw_error( + datafeed_loop_v1, +): datafeed_loop_v1._running = True - with pytest.raises(RuntimeError, match="The datafeed service V1 is already started"): + with pytest.raises( + RuntimeError, match="The datafeed service V1 is already started" + ): await datafeed_loop_v1.start() @@ -156,7 +184,9 @@ async def test_read_datafeed_empty_list(datafeed_loop_v1, datafeed_api): @pytest.mark.asyncio -async def test_read_datafeed_non_empty_list(datafeed_loop_v1, datafeed_api, message_sent): +async def test_read_datafeed_non_empty_list( + datafeed_loop_v1, datafeed_api, message_sent +): events = [message_sent] datafeed_api.v4_datafeed_id_read_get.return_value = EventsMock(events) @@ -164,45 +194,61 @@ async def test_read_datafeed_non_empty_list(datafeed_loop_v1, datafeed_api, mess @pytest.mark.asyncio -async def test_datafeed_is_reused(datafeed_repository, datafeed_api, session_service, auth_session, config, - read_df_side_effect): +async def test_datafeed_is_reused( + datafeed_repository, + datafeed_api, + session_service, + auth_session, + config, + read_df_side_effect, +): datafeed_repository.write("persisted_id") - datafeed_loop = auto_stopping_datafeed_loop_v1(datafeed_api, session_service, auth_session, config, - datafeed_repository) + datafeed_loop = auto_stopping_datafeed_loop_v1( + datafeed_api, session_service, auth_session, config, datafeed_repository + ) datafeed_api.v4_datafeed_id_read_get.side_effect = read_df_side_effect await datafeed_loop.start() datafeed_api.v4_datafeed_create_post.assert_not_called() - datafeed_api.v4_datafeed_id_read_get.assert_called_with(id="persisted_id", - session_token="session_token", - key_manager_token="km_token") + datafeed_api.v4_datafeed_id_read_get.assert_called_with( + id="persisted_id", session_token="session_token", key_manager_token="km_token" + ) @pytest.mark.asyncio -async def test_start_recreate_datafeed_error(datafeed_repository, datafeed_api, session_service, auth_session, config): +async def test_start_recreate_datafeed_error( + datafeed_repository, datafeed_api, session_service, auth_session, config +): datafeed_repository.write("persisted_id") - datafeed_loop = auto_stopping_datafeed_loop_v1(datafeed_api, session_service, auth_session, config, - datafeed_repository) + datafeed_loop = auto_stopping_datafeed_loop_v1( + datafeed_api, session_service, auth_session, config, datafeed_repository + ) - datafeed_api.v4_datafeed_id_read_get.side_effect = ApiException(400, "Expired Datafeed id") - datafeed_api.v4_datafeed_create_post.side_effect = ApiException(400, "Unhandled exception") + datafeed_api.v4_datafeed_id_read_get.side_effect = ApiException( + 400, "Expired Datafeed id" + ) + datafeed_api.v4_datafeed_create_post.side_effect = ApiException( + 400, "Unhandled exception" + ) with pytest.raises(ApiException): await datafeed_loop.start() - datafeed_api.v4_datafeed_id_read_get.assert_called_once_with(id="persisted_id", - session_token="session_token", - key_manager_token="km_token") + datafeed_api.v4_datafeed_id_read_get.assert_called_once_with( + id="persisted_id", session_token="session_token", key_manager_token="km_token" + ) - datafeed_api.v4_datafeed_create_post.assert_called_once_with(session_token="session_token", - key_manager_token="km_token") + datafeed_api.v4_datafeed_create_post.assert_called_once_with( + session_token="session_token", key_manager_token="km_token" + ) @pytest.mark.asyncio -async def test_retrieve_datafeed_from_datafeed_file(tmpdir, datafeed_api, session_service, auth_session, config, - read_df_side_effect): +async def test_retrieve_datafeed_from_datafeed_file( + tmpdir, datafeed_api, session_service, auth_session, config, read_df_side_effect +): datafeed_file_content = get_resource_content("datafeed/datafeedId") datafeed_file_path = tmpdir.join("datafeed.id") datafeed_file_path.write(datafeed_file_content) @@ -210,7 +256,9 @@ async def test_retrieve_datafeed_from_datafeed_file(tmpdir, datafeed_api, sessio datafeed_config = BdkDatafeedConfig({"idFilePath": str(datafeed_file_path)}) config.datafeed = datafeed_config - datafeed_loop = auto_stopping_datafeed_loop_v1(datafeed_api, session_service, auth_session, config) + datafeed_loop = auto_stopping_datafeed_loop_v1( + datafeed_api, session_service, auth_session, config + ) datafeed_api.v4_datafeed_id_read_get.side_effect = read_df_side_effect await datafeed_loop.start() @@ -218,8 +266,9 @@ async def test_retrieve_datafeed_from_datafeed_file(tmpdir, datafeed_api, sessio @pytest.mark.asyncio -async def test_retrieve_datafeed_from_invalid_datafeed_dir(tmpdir, datafeed_api, session_service, auth_session, config, - read_df_side_effect): +async def test_retrieve_datafeed_from_invalid_datafeed_dir( + tmpdir, datafeed_api, session_service, auth_session, config, read_df_side_effect +): datafeed_id_file_content = get_resource_content("datafeed/datafeedId") datafeed_id_file_path = tmpdir.join("datafeed.id") datafeed_id_file_path.write(datafeed_id_file_content) @@ -227,7 +276,9 @@ async def test_retrieve_datafeed_from_invalid_datafeed_dir(tmpdir, datafeed_api, datafeed_config = BdkDatafeedConfig({"idFilePath": str(tmpdir)}) config.datafeed = datafeed_config - datafeed_loop = auto_stopping_datafeed_loop_v1(datafeed_api, session_service, auth_session, config) + datafeed_loop = auto_stopping_datafeed_loop_v1( + datafeed_api, session_service, auth_session, config + ) datafeed_api.v4_datafeed_id_read_get.side_effect = read_df_side_effect await datafeed_loop.start() @@ -235,23 +286,31 @@ async def test_retrieve_datafeed_from_invalid_datafeed_dir(tmpdir, datafeed_api, @pytest.mark.asyncio -async def test_retrieve_datafeed_id_from_unknown_path(datafeed_api, session_service, auth_session, config): +async def test_retrieve_datafeed_id_from_unknown_path( + datafeed_api, session_service, auth_session, config +): datafeed_config = BdkDatafeedConfig({"idFilePath": "unknown_path"}) config.datafeed = datafeed_config - datafeed_loop = auto_stopping_datafeed_loop_v1(datafeed_api, session_service, auth_session, config) + datafeed_loop = auto_stopping_datafeed_loop_v1( + datafeed_api, session_service, auth_session, config + ) assert datafeed_loop._datafeed_id is None @pytest.mark.asyncio -async def test_retrieve_datafeed_id_from_empty_file(tmpdir, datafeed_api, session_service, auth_session, config): +async def test_retrieve_datafeed_id_from_empty_file( + tmpdir, datafeed_api, session_service, auth_session, config +): datafeed_file_path = tmpdir.join("datafeed.id") datafeed_config = BdkDatafeedConfig({"idFilePath": str(datafeed_file_path)}) config.datafeed = datafeed_config - datafeed_loop = auto_stopping_datafeed_loop_v1(datafeed_api, auth_session, session_service, config) + datafeed_loop = auto_stopping_datafeed_loop_v1( + datafeed_api, auth_session, session_service, config + ) assert datafeed_loop._datafeed_id is None @@ -276,7 +335,6 @@ async def is_accepting_event(self, event: V4Event, username: str) -> bool: @pytest.mark.asyncio async def test_listener_called(mock_datafeed_loop_v1, message_sent, initiator_username): class RealTimeEventListenerImpl(RealTimeEventListener): - async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): await mock_datafeed_loop_v1.stop() @@ -285,11 +343,15 @@ async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): await mock_datafeed_loop_v1.start() - listener.on_message_sent.assert_called_once_with(initiator_username, message_sent.payload.message_sent) + listener.on_message_sent.assert_called_once_with( + initiator_username, message_sent.payload.message_sent + ) @pytest.mark.asyncio -async def test_exception_in_listener_ignored(mock_datafeed_loop_v1, message_sent, initiator_username): +async def test_exception_in_listener_ignored( + mock_datafeed_loop_v1, message_sent, initiator_username +): class RealTimeEventListenerImpl(RealTimeEventListener): count = 0 @@ -309,7 +371,9 @@ async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): @pytest.mark.asyncio -async def test_event_error_in_listener_ignored(mock_datafeed_loop_v1, message_sent, initiator_username): +async def test_event_error_in_listener_ignored( + mock_datafeed_loop_v1, message_sent, initiator_username +): class RealTimeEventListenerImpl(RealTimeEventListener): count = 0 @@ -329,7 +393,9 @@ async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): @pytest.mark.asyncio -async def test_events_concurrency_within_same_read_df_chunk(mock_datafeed_loop_v1, message_sent): +async def test_events_concurrency_within_same_read_df_chunk( + mock_datafeed_loop_v1, message_sent +): class QueueListener(RealTimeEventListener): def __init__(self): self.queue = asyncio.Queue() diff --git a/tests/core/service/datafeed/datafeed_loop_v2_test.py b/tests/core/service/datafeed/datafeed_loop_v2_test.py index 1fbd3b3a..f07d0b77 100644 --- a/tests/core/service/datafeed/datafeed_loop_v2_test.py +++ b/tests/core/service/datafeed/datafeed_loop_v2_test.py @@ -1,16 +1,22 @@ import asyncio -from unittest.mock import MagicMock, AsyncMock, call +from unittest.mock import AsyncMock, MagicMock, call import pytest -from tests.core.service.datafeed.test_fixtures import fixture_initiator_username, fixture_session_service, \ - fixture_auth_session +# ruff: noqa +from tests.core.service.datafeed.test_fixtures import ( + fixture_initiator_username, + fixture_session_service, + fixture_auth_session, +) from symphony.bdk.core.config.loader import BdkConfigLoader from symphony.bdk.core.service.datafeed.abstract_datafeed_loop import RealTimeEvent from symphony.bdk.core.service.datafeed.datafeed_loop_v2 import DatafeedLoopV2 from symphony.bdk.core.service.datafeed.exception import EventError -from symphony.bdk.core.service.datafeed.real_time_event_listener import RealTimeEventListener +from symphony.bdk.core.service.datafeed.real_time_event_listener import ( + RealTimeEventListener, +) from symphony.bdk.gen import ApiClient, ApiException from symphony.bdk.gen.agent_api.datafeed_api import DatafeedApi from symphony.bdk.gen.agent_model.ack_id import AckId @@ -75,9 +81,11 @@ def fixture_mock_listener(): @pytest.fixture(name="message_sent") def fixture_message_sent(initiator_username): - return V4Event(type=RealTimeEvent.MESSAGESENT.name, - payload=V4Payload(message_sent=V4MessageSent()), - initiator=initiator_username) + return V4Event( + type=RealTimeEvent.MESSAGESENT.name, + payload=V4Payload(message_sent=V4MessageSent()), + initiator=initiator_username, + ) @pytest.fixture(name="message_sent_event") @@ -95,7 +103,6 @@ def fixture_datafeed_loop(datafeed_api, session_service, auth_session, config): datafeed_loop = DatafeedLoopV2(datafeed_api, session_service, auth_session, config) class RealTimeEventListenerImpl(RealTimeEventListener): - async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): await datafeed_loop.stop() @@ -124,19 +131,20 @@ async def test_start(datafeed_loop, datafeed_api, session_service, read_df_side_ session_service.get_session.assert_called_once() datafeed_api.list_datafeed.assert_called_with( - session_token="session_token", - key_manager_token="km_token" + session_token="session_token", key_manager_token="km_token" ) datafeed_api.create_datafeed.assert_called_with( session_token="session_token", key_manager_token="km_token", - body=V5DatafeedCreateBody() + body=V5DatafeedCreateBody(), ) - assert datafeed_api.read_datafeed.call_args_list[0].kwargs == {"session_token": "session_token", - "key_manager_token": "km_token", - "datafeed_id": "test_id", - "ack_id": AckId(ack_id="", update_presence=True)} + assert datafeed_api.read_datafeed.call_args_list[0].kwargs == { + "session_token": "session_token", + "key_manager_token": "km_token", + "datafeed_id": "test_id", + "ack_id": AckId(ack_id="", update_presence=True), + } assert datafeed_loop._datafeed_id == "test_id" assert datafeed_loop._ack_id == "ack_id" @@ -144,25 +152,30 @@ async def test_start(datafeed_loop, datafeed_api, session_service, read_df_side_ @pytest.mark.asyncio async def test_start_already_started_datafeed_v2_loop_should_throw_error(datafeed_loop): datafeed_loop._running = True - with pytest.raises(RuntimeError, match="The datafeed service V2 is already started"): + with pytest.raises( + RuntimeError, match="The datafeed service V2 is already started" + ): await datafeed_loop.start() @pytest.mark.asyncio -async def test_start_old_datafeed_format_exist(datafeed_loop, datafeed_api, read_df_side_effect): +async def test_start_old_datafeed_format_exist( + datafeed_loop, datafeed_api, read_df_side_effect +): datafeed_api.list_datafeed.return_value = [V5Datafeed(id="abc_f")] datafeed_api.read_datafeed.side_effect = read_df_side_effect await datafeed_loop.start() datafeed_api.list_datafeed.assert_called_with( - session_token="session_token", - key_manager_token="km_token" + session_token="session_token", key_manager_token="km_token" ) - assert datafeed_api.read_datafeed.call_args_list[0].kwargs == {"session_token": "session_token", - "key_manager_token": "km_token", - "datafeed_id": "abc_f", - "ack_id": AckId(ack_id="", update_presence=True)} + assert datafeed_api.read_datafeed.call_args_list[0].kwargs == { + "session_token": "session_token", + "key_manager_token": "km_token", + "datafeed_id": "abc_f", + "ack_id": AckId(ack_id="", update_presence=True), + } assert datafeed_loop._datafeed_id == "abc_f" assert datafeed_loop._ack_id == "ack_id" @@ -175,13 +188,14 @@ async def test_start_datafeed_exist(datafeed_loop, datafeed_api, read_df_side_ef await datafeed_loop.start() datafeed_api.list_datafeed.assert_called_with( - session_token="session_token", - key_manager_token="km_token" + session_token="session_token", key_manager_token="km_token" ) - assert datafeed_api.read_datafeed.call_args_list[0].kwargs == {"session_token": "session_token", - "key_manager_token": "km_token", - "datafeed_id": "abc_f_def", - "ack_id": AckId(ack_id="", update_presence=True)} + assert datafeed_api.read_datafeed.call_args_list[0].kwargs == { + "session_token": "session_token", + "key_manager_token": "km_token", + "datafeed_id": "abc_f_def", + "ack_id": AckId(ack_id="", update_presence=True), + } assert datafeed_loop._datafeed_id == "abc_f_def" assert datafeed_loop._ack_id == "ack_id" @@ -195,13 +209,14 @@ async def test_read_datafeed_bad_id(datafeed_loop, datafeed_api, read_df_side_ef await datafeed_loop.start() datafeed_api.list_datafeed.assert_called_with( - session_token="session_token", - key_manager_token="km_token" + session_token="session_token", key_manager_token="km_token" ) - assert datafeed_api.read_datafeed.call_args_list[0].kwargs == {"session_token": "session_token", - "key_manager_token": "km_token", - "datafeed_id": "abc_f_def", - "ack_id": AckId(ack_id="", update_presence=True)} + assert datafeed_api.read_datafeed.call_args_list[0].kwargs == { + "session_token": "session_token", + "key_manager_token": "km_token", + "datafeed_id": "abc_f_def", + "ack_id": AckId(ack_id="", update_presence=True), + } datafeed_api.create_datafeed.assert_called_once() @@ -210,7 +225,9 @@ async def test_read_datafeed_bad_id(datafeed_loop, datafeed_api, read_df_side_ef @pytest.mark.asyncio -async def test_start_datafeed_stale_datafeed(datafeed_loop, datafeed_api, session_service, message_sent_event): +async def test_start_datafeed_stale_datafeed( + datafeed_loop, datafeed_api, session_service, message_sent_event +): datafeed_loop._retry_config = minimal_retry_config_with_attempts(2) datafeed_api.list_datafeed.return_value = [V5Datafeed(id="abc_f_def")] datafeed_api.create_datafeed.return_value = V5Datafeed(id="test_id") @@ -232,36 +249,37 @@ async def raise_and_return_event(**kwargs): session_service.get_session.assert_called_once() datafeed_api.list_datafeed.assert_called_with( - session_token="session_token", - key_manager_token="km_token" + session_token="session_token", key_manager_token="km_token" ) datafeed_api.delete_datafeed.assert_called_with( session_token="session_token", key_manager_token="km_token", - datafeed_id="abc_f_def" + datafeed_id="abc_f_def", ) datafeed_api.create_datafeed.assert_called_with( session_token="session_token", key_manager_token="km_token", - body=V5DatafeedCreateBody() + body=V5DatafeedCreateBody(), ) - datafeed_api.read_datafeed.assert_has_calls([ - call( - session_token="session_token", - key_manager_token="km_token", - datafeed_id="abc_f_def", - ack_id=AckId(ack_id="", update_presence=True) - ), - call( - session_token="session_token", - key_manager_token="km_token", - datafeed_id="test_id", - ack_id=AckId(ack_id="", update_presence=True) - ) - ]) + datafeed_api.read_datafeed.assert_has_calls( + [ + call( + session_token="session_token", + key_manager_token="km_token", + datafeed_id="abc_f_def", + ack_id=AckId(ack_id="", update_presence=True), + ), + call( + session_token="session_token", + key_manager_token="km_token", + datafeed_id="test_id", + ack_id=AckId(ack_id="", update_presence=True), + ), + ] + ) assert datafeed_loop._datafeed_id == "test_id" assert datafeed_loop._ack_id == "ack_id" @@ -290,42 +308,62 @@ async def test_read_datafeed_empty_list(mock_datafeed_loop, mock_listener): @pytest.mark.asyncio -async def test_read_datafeed_non_empty_list(mock_datafeed_loop, mock_listener, message_sent): - mock_datafeed_loop._read_events.side_effect = read_df_function(EventsMock([message_sent])) +async def test_read_datafeed_non_empty_list( + mock_datafeed_loop, mock_listener, message_sent +): + mock_datafeed_loop._read_events.side_effect = read_df_function( + EventsMock([message_sent]) + ) mock_datafeed_loop.subscribe(mock_listener) await start_and_stop_df_loop(mock_datafeed_loop) assert mock_datafeed_loop._ack_id == ACK_ID - mock_listener.on_message_sent.assert_called_with(message_sent.initiator, message_sent.payload.message_sent) + mock_listener.on_message_sent.assert_called_with( + message_sent.initiator, message_sent.payload.message_sent + ) @pytest.mark.asyncio -async def test_read_datafeed_error_in_listener(mock_datafeed_loop, mock_listener, message_sent): +async def test_read_datafeed_error_in_listener( + mock_datafeed_loop, mock_listener, message_sent +): mock_listener.on_message_sent.side_effect = ValueError() - mock_datafeed_loop._read_events.side_effect = read_df_function(EventsMock([message_sent])) + mock_datafeed_loop._read_events.side_effect = read_df_function( + EventsMock([message_sent]) + ) mock_datafeed_loop.subscribe(mock_listener) await start_and_stop_df_loop(mock_datafeed_loop) assert mock_datafeed_loop._ack_id == ACK_ID - mock_listener.on_message_sent.assert_called_with(message_sent.initiator, message_sent.payload.message_sent) + mock_listener.on_message_sent.assert_called_with( + message_sent.initiator, message_sent.payload.message_sent + ) @pytest.mark.asyncio -async def test_read_datafeed_event_error_in_listener(mock_datafeed_loop, mock_listener, message_sent): +async def test_read_datafeed_event_error_in_listener( + mock_datafeed_loop, mock_listener, message_sent +): mock_listener.on_message_sent.side_effect = EventError() - mock_datafeed_loop._read_events.side_effect = read_df_function(EventsMock([message_sent])) + mock_datafeed_loop._read_events.side_effect = read_df_function( + EventsMock([message_sent]) + ) mock_datafeed_loop.subscribe(mock_listener) await start_and_stop_df_loop(mock_datafeed_loop) assert mock_datafeed_loop._ack_id == "" # ack id not updated - mock_listener.on_message_sent.assert_called_with(message_sent.initiator, message_sent.payload.message_sent) + mock_listener.on_message_sent.assert_called_with( + message_sent.initiator, message_sent.payload.message_sent + ) @pytest.mark.asyncio -async def test_events_concurrency_within_same_read_df_chunk(mock_datafeed_loop, message_sent): +async def test_events_concurrency_within_same_read_df_chunk( + mock_datafeed_loop, message_sent +): class QueueListener(RealTimeEventListener): def __init__(self): self.queue = asyncio.Queue() @@ -339,7 +377,9 @@ async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): elif self.count == 2: await self.queue.put("message") - mock_datafeed_loop._read_events.side_effect = read_df_function(EventsMock([message_sent, message_sent])) + mock_datafeed_loop._read_events.side_effect = read_df_function( + EventsMock([message_sent, message_sent]) + ) mock_datafeed_loop.subscribe(QueueListener()) await mock_datafeed_loop.start() # test no deadlock @@ -349,7 +389,10 @@ async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): async def test_400_should_call_recreate_df_and_retry(datafeed_loop, datafeed_api): datafeed_loop._retry_config = minimal_retry_config_with_attempts(2) datafeed_loop.recreate_datafeed = AsyncMock() - datafeed_api.read_datafeed.side_effect = [ApiException(status=400), ApiException(status=500)] + datafeed_api.read_datafeed.side_effect = [ + ApiException(status=400), + ApiException(status=500), + ] with pytest.raises(ApiException) as exception: await datafeed_loop.start() @@ -360,7 +403,9 @@ async def test_400_should_call_recreate_df_and_retry(datafeed_loop, datafeed_api @pytest.mark.asyncio -async def test_400_should_call_recreate_df_return_and_retry(datafeed_loop, datafeed_api, message_sent_event): +async def test_400_should_call_recreate_df_return_and_retry( + datafeed_loop, datafeed_api, message_sent_event +): async def read_df(**kwargs): if read_df.first_time: read_df.first_time = False @@ -382,7 +427,9 @@ async def read_df(**kwargs): @pytest.mark.asyncio -async def test_unexpected_error_should_be_propagated_and_call_stop_tasks(datafeed_loop, datafeed_api): +async def test_unexpected_error_should_be_propagated_and_call_stop_tasks( + datafeed_loop, datafeed_api +): exception = ValueError("An error") datafeed_api.read_datafeed.side_effect = exception diff --git a/tests/core/service/datafeed/datahose_loop_test.py b/tests/core/service/datafeed/datahose_loop_test.py index 964aab0e..f4ac4439 100644 --- a/tests/core/service/datafeed/datahose_loop_test.py +++ b/tests/core/service/datafeed/datahose_loop_test.py @@ -1,22 +1,29 @@ +# ruff: noqa import asyncio + import pytest from unittest.mock import MagicMock, AsyncMock -from tests.core.service.datafeed.test_fixtures import fixture_initiator_username, fixture_session_service, \ - fixture_message_sent_events_mock +from tests.core.service.datafeed.test_fixtures import ( + fixture_initiator_username, + fixture_session_service, + fixture_message_sent_events_mock, +) -from symphony.bdk.gen.agent_model.v5_event_list import V5EventList -from symphony.bdk.core.service.datafeed.datahose_loop import DatahoseLoop from symphony.bdk.core.auth.auth_session import AuthSession from symphony.bdk.core.config.loader import BdkConfigLoader from symphony.bdk.core.service.datafeed.abstract_datafeed_loop import RealTimeEvent -from symphony.bdk.core.service.datafeed.real_time_event_listener import RealTimeEventListener +from symphony.bdk.core.service.datafeed.datahose_loop import DatahoseLoop +from symphony.bdk.core.service.datafeed.real_time_event_listener import ( + RealTimeEventListener, +) from symphony.bdk.gen import ApiClient from symphony.bdk.gen.agent_api.datafeed_api import DatafeedApi +from symphony.bdk.gen.agent_model.v4_event import V4Event from symphony.bdk.gen.agent_model.v4_initiator import V4Initiator from symphony.bdk.gen.agent_model.v4_message_sent import V4MessageSent -from symphony.bdk.gen.agent_model.v4_event import V4Event from symphony.bdk.gen.agent_model.v4_payload import V4Payload +from symphony.bdk.gen.agent_model.v5_event_list import V5EventList from symphony.bdk.gen.agent_model.v5_events_read_body import V5EventsReadBody from tests.core.config import minimal_retry_config from tests.utils.resource_utils import get_config_resource_filepath @@ -56,7 +63,6 @@ def fixture_datahose_loop(datafeed_api, session_service, auth_session, config): datahose_loop = DatahoseLoop(datafeed_api, session_service, auth_session, config) class RealTimeEventListenerImpl(RealTimeEventListener): - async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): await datahose_loop.stop() @@ -66,9 +72,11 @@ async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): @pytest.fixture(name="message_sent") def fixture_message_sent(initiator_username): - return V4Event(type=RealTimeEvent.MESSAGESENT.name, - payload=V4Payload(message_sent=V4MessageSent()), - initiator=initiator_username) + return V4Event( + type=RealTimeEvent.MESSAGESENT.name, + payload=V4Payload(message_sent=V4MessageSent()), + initiator=initiator_username, + ) @pytest.fixture(name="read_events_side_effect") @@ -84,17 +92,18 @@ async def read_events(**kwargs): @pytest.mark.asyncio -async def test_start(datahose_loop, datafeed_api, session_service, message_sent_events_mock): +async def test_start( + datahose_loop, datafeed_api, session_service, message_sent_events_mock +): datafeed_api.read_events.return_value = message_sent_events_mock - body = V5EventsReadBody(type="datahose", tag="TEST_TAG", - event_types=["SOCIALMESSAGE"], ack_id="") + body = V5EventsReadBody( + type="datahose", tag="TEST_TAG", event_types=["SOCIALMESSAGE"], ack_id="" + ) await datahose_loop.start() session_service.get_session.assert_called_once() datafeed_api.read_events.assert_called_with( - session_token="session_token", - key_manager_token="km_token", - body=body + session_token="session_token", key_manager_token="km_token", body=body ) assert datahose_loop._ack_id == "test_events_ack_id" diff --git a/tests/core/service/datafeed/test_fixtures.py b/tests/core/service/datafeed/test_fixtures.py index 222ee22c..8acaf7bc 100644 --- a/tests/core/service/datafeed/test_fixtures.py +++ b/tests/core/service/datafeed/test_fixtures.py @@ -1,4 +1,3 @@ -import asyncio from unittest.mock import AsyncMock, MagicMock import pytest @@ -58,10 +57,12 @@ def fixture_datafeed_api(): @pytest.fixture(name="message_sent_v4_event") def fixture_message_sent_v4_event(initiator_userid): - payload = V4Payload(message_sent=V4MessageSent(message=V4Message(attachments=[], message="message"))) + payload = V4Payload( + message_sent=V4MessageSent(message=V4Message(attachments=[], message="message")) + ) return V4Event(type=RealTimeEvent.MESSAGESENT.name, payload=payload, initiator=initiator_userid) @pytest.fixture(name="message_sent_events_mock") def fixture_message_sent_events_mock(message_sent): - return EventsMock([message_sent], "test_events_ack_id") \ No newline at end of file + return EventsMock([message_sent], "test_events_ack_id") diff --git a/tests/core/service/health/health_service_test.py b/tests/core/service/health/health_service_test.py index 372ef332..a2defd64 100644 --- a/tests/core/service/health/health_service_test.py +++ b/tests/core/service/health/health_service_test.py @@ -1,15 +1,15 @@ -from unittest.mock import MagicMock, AsyncMock +from unittest.mock import AsyncMock, MagicMock + import pytest from symphony.bdk.core.service.health.health_service import HealthService from symphony.bdk.gen import ApiException -from symphony.bdk.gen.agent_api.system_api import SystemApi from symphony.bdk.gen.agent_api.signals_api import SignalsApi +from symphony.bdk.gen.agent_api.system_api import SystemApi from symphony.bdk.gen.agent_model.agent_info import AgentInfo from symphony.bdk.gen.agent_model.v3_health import V3Health - -from tests.utils.resource_utils import get_deserialized_object_from_resource from tests.core.config import minimal_retry_config +from tests.utils.resource_utils import get_deserialized_object_from_resource @pytest.fixture(name="mocked_system_api_client") @@ -34,8 +34,9 @@ def fixture_health_service(mocked_system_api_client, mocked_signals_api_client): @pytest.mark.asyncio async def test_health_check(health_service, mocked_system_api_client): - mocked_system_api_client.v3_health.return_value = \ - get_deserialized_object_from_resource(V3Health, resource_path="health_response/health_check.json") + mocked_system_api_client.v3_health.return_value = get_deserialized_object_from_resource( + V3Health, resource_path="health_response/health_check.json" + ) health = await health_service.health_check() @@ -52,8 +53,11 @@ async def test_health_check_failed(health_service, mocked_system_api_client): @pytest.mark.asyncio async def test_health_check_extended(health_service, mocked_system_api_client): - mocked_system_api_client.v3_extended_health.return_value = \ - get_deserialized_object_from_resource(V3Health, resource_path="health_response/health_check_extended.json") + mocked_system_api_client.v3_extended_health.return_value = ( + get_deserialized_object_from_resource( + V3Health, resource_path="health_response/health_check_extended.json" + ) + ) health = await health_service.health_check_extended() assert health.status.value == "UP" @@ -72,8 +76,9 @@ async def test_health_check_extended_failed(health_service, mocked_system_api_cl @pytest.mark.asyncio async def test_get_agent_info(health_service, mocked_signals_api_client): - mocked_signals_api_client.v1_info_get.return_value = \ - get_deserialized_object_from_resource(AgentInfo, resource_path="health_response/agent_info.json") + mocked_signals_api_client.v1_info_get.return_value = get_deserialized_object_from_resource( + AgentInfo, resource_path="health_response/agent_info.json" + ) agent_info = await health_service.get_agent_info() diff --git a/tests/core/service/message/message_parser_test.py b/tests/core/service/message/message_parser_test.py index 16287492..6f373c5c 100644 --- a/tests/core/service/message/message_parser_test.py +++ b/tests/core/service/message/message_parser_test.py @@ -3,8 +3,13 @@ import pytest from symphony.bdk.core.service.exception import MessageParserError -from symphony.bdk.core.service.message.message_parser import get_text_content_from_message, get_mentions, \ - get_hashtags, get_cashtags, get_emojis +from symphony.bdk.core.service.message.message_parser import ( + get_cashtags, + get_emojis, + get_hashtags, + get_mentions, + get_text_content_from_message, +) from symphony.bdk.gen.agent_model.v4_message import V4Message from tests.utils.resource_utils import get_resource_content @@ -22,37 +27,49 @@ def assert_message_has_text_content(actual_message, expected_text_content): @pytest.fixture(name="message_with_data") def fixture_message_with_data(): - return create_v4_message("", - get_resource_content("utils/message_entity_data.json")) + return create_v4_message( + '', + get_resource_content("utils/message_entity_data.json"), + ) @pytest.fixture(name="message_with_invalid_data") def fixture_message_with_invalid_data(): - return create_v4_message("
    \n", "unparsable json data") + return create_v4_message( + '
    \n', "unparsable json data" + ) def test_get_text_content_from_message(): - message = create_v4_message("") + message = create_v4_message( + '' + ) assert_message_has_text_content(message, "This is a link to Symphony's Website") def test_get_text_content_from_escaped_message_ampersand(): - escaped_message_ampersand = create_v4_message("
    \n" - "This is some escaped text &
    ") + escaped_message_ampersand = create_v4_message( + '
    \n' + "This is some escaped text &
    " + ) assert_message_has_text_content(escaped_message_ampersand, "This is some escaped text &") def test_get_text_content_from_escaped_message_lt(): - escaped_message_lt = create_v4_message("
    \n" - "This is some escaped text <
    ") + escaped_message_lt = create_v4_message( + '
    \n' + "This is some escaped text <
    " + ) assert_message_has_text_content(escaped_message_lt, "This is some escaped text <") def test_get_text_content_from_message_with_html_entities(): - message_with_nbsp = create_v4_message("
    \n" - "This is some escaped text  
    ") + message_with_nbsp = create_v4_message( + '
    \n' + "This is some escaped text  
    " + ) with pytest.raises(MessageParserError): get_text_content_from_message(message_with_nbsp) diff --git a/tests/core/service/message/message_service_test.py b/tests/core/service/message/message_service_test.py index c97b49f9..27ea9497 100644 --- a/tests/core/service/message/message_service_test.py +++ b/tests/core/service/message/message_service_test.py @@ -1,12 +1,14 @@ import json -from unittest.mock import MagicMock, AsyncMock +from unittest.mock import AsyncMock, MagicMock import pytest from symphony.bdk.core.auth.auth_session import AuthSession from symphony.bdk.core.service.message.message_service import MessageService from symphony.bdk.core.service.message.model import Message -from symphony.bdk.core.service.message.multi_attachments_messages_api import MultiAttachmentsMessagesApi +from symphony.bdk.core.service.message.multi_attachments_messages_api import ( + MultiAttachmentsMessagesApi, +) from symphony.bdk.gen.agent_api.attachments_api import AttachmentsApi from symphony.bdk.gen.agent_model.message_search_query import MessageSearchQuery from symphony.bdk.gen.agent_model.v4_import_response_list import V4ImportResponseList @@ -27,7 +29,7 @@ from symphony.bdk.gen.pod_model.stream_attachment_response import StreamAttachmentResponse from symphony.bdk.gen.pod_model.string_list import StringList from tests.core.config import minimal_retry_config -from tests.utils.resource_utils import get_deserialized_object_from_resource, deserialize_object +from tests.utils.resource_utils import deserialize_object, get_deserialized_object_from_resource @pytest.fixture(name="auth_session") @@ -57,15 +59,16 @@ def fixture_message_service(mocked_api_client, auth_session): AttachmentsApi(mocked_api_client), DefaultApi(mocked_api_client), auth_session, - minimal_retry_config() + minimal_retry_config(), ) return service @pytest.mark.asyncio async def test_list_messages(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(V4MessageList, "message_response/list_messages.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V4MessageList, "message_response/list_messages.json" + ) messages_list = await message_service.list_messages("stream_id") assert len(messages_list) == 1 @@ -74,8 +77,9 @@ async def test_list_messages(mocked_api_client, message_service): @pytest.mark.asyncio async def test_send_message(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(V4Message, "message_response/message.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V4Message, "message_response/message.json" + ) message = await message_service.send_message("stream_id", "test_message") assert message.message_id == "-AANBHKtUC-2q6_0WSjBGX___pHnSfBKdA" @@ -85,7 +89,10 @@ async def test_send_message(mocked_api_client, message_service): @pytest.mark.asyncio async def test_send_simple_message(message_service): message_service._send_message = AsyncMock( - return_value=get_deserialized_object_from_resource(V4Message, "message_response/message.json")) + return_value=get_deserialized_object_from_resource( + V4Message, "message_response/message.json" + ) + ) message = "Hello" stream_id = "stream_id" @@ -97,18 +104,26 @@ async def test_send_simple_message(message_service): @pytest.mark.asyncio async def test_send_simple_message_without_mml_tags(message_service): message_service._send_message = AsyncMock( - return_value=get_deserialized_object_from_resource(V4Message, "message_response/message.json")) + return_value=get_deserialized_object_from_resource( + V4Message, "message_response/message.json" + ) + ) stream_id = "stream_id" await message_service.send_message(stream_id, "Hello") - message_service._send_message.assert_called_once_with(stream_id, "Hello", "", "", [], []) + message_service._send_message.assert_called_once_with( + stream_id, "Hello", "", "", [], [] + ) @pytest.mark.asyncio async def test_send_message_with_data(message_service): message_service._send_message = AsyncMock( - return_value=get_deserialized_object_from_resource(V4Message, "message_response/message.json")) + return_value=get_deserialized_object_from_resource( + V4Message, "message_response/message.json" + ) + ) message = "Hello" stream_id = "stream_id" data = ["foo", {"bar": ("baz", 1.0, 2)}] @@ -122,7 +137,10 @@ async def test_send_message_with_data(message_service): @pytest.mark.asyncio async def test_send_complex_message(message_service): message_service._send_message = AsyncMock( - return_value=get_deserialized_object_from_resource(V4Message, "message_response/message.json")) + return_value=get_deserialized_object_from_resource( + V4Message, "message_response/message.json" + ) + ) stream_id = "stream_id" content = "Hello" data = ["foo", {"bar": ("baz", 1.0, 2)}] @@ -130,19 +148,25 @@ async def test_send_complex_message(message_service): version = "2.0" attachment = "attachment" preview = "preview" - message = Message(content=content, data=data, attachments=[(attachment, preview)], version=version) + message = Message( + content=content, data=data, attachments=[(attachment, preview)], version=version + ) await message_service.send_message(stream_id, message) - message_service._send_message.assert_called_once_with(stream_id, content, json_data, version, [attachment], - [preview]) + message_service._send_message.assert_called_once_with( + stream_id, content, json_data, version, [attachment], [preview] + ) @pytest.mark.asyncio async def test_blast_message(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(V4MessageBlastResponse, "message_response/blast_message.json") - blast_message = await message_service.blast_message(["stream_id1", "stream_id2"], "test_message") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V4MessageBlastResponse, "message_response/blast_message.json" + ) + blast_message = await message_service.blast_message( + ["stream_id1", "stream_id2"], "test_message" + ) assert len(blast_message.messages) == 2 assert blast_message.messages[0].message_id == "-AANBHKtUC-2q6_0WSjBGX___pHnSfBKdA-1" @@ -151,8 +175,9 @@ async def test_blast_message(mocked_api_client, message_service): @pytest.mark.asyncio async def test_blast_simple_message(message_service): - blast_message_response = get_deserialized_object_from_resource(V4MessageBlastResponse, - "message_response/blast_message.json") + blast_message_response = get_deserialized_object_from_resource( + V4MessageBlastResponse, "message_response/blast_message.json" + ) message_service._blast_message = AsyncMock(return_value=blast_message_response) stream_ids = ["sid1", "sid2"] message = "Hello" @@ -164,19 +189,23 @@ async def test_blast_simple_message(message_service): @pytest.mark.asyncio async def test_blast_simple_message_without_mml_tags(message_service): - blast_message_response = get_deserialized_object_from_resource(V4MessageBlastResponse, - "message_response/blast_message.json") + blast_message_response = get_deserialized_object_from_resource( + V4MessageBlastResponse, "message_response/blast_message.json" + ) message_service._blast_message = AsyncMock(return_value=blast_message_response) stream_ids = ["sid1", "sid2"] await message_service.blast_message(stream_ids, "Hello") - message_service._blast_message.assert_called_once_with(stream_ids, "Hello", "", "", [], []) + message_service._blast_message.assert_called_once_with( + stream_ids, "Hello", "", "", [], [] + ) @pytest.mark.asyncio async def test_blast_message_with_data(message_service): - blast_message_response = get_deserialized_object_from_resource(V4MessageBlastResponse, - "message_response/blast_message.json") + blast_message_response = get_deserialized_object_from_resource( + V4MessageBlastResponse, "message_response/blast_message.json" + ) message_service._blast_message = AsyncMock(return_value=blast_message_response) stream_ids = ["sid1", "sid2"] message = "Hello" @@ -185,13 +214,16 @@ async def test_blast_message_with_data(message_service): await message_service.blast_message(stream_ids, message, data) - message_service._blast_message.assert_called_once_with(stream_ids, message, json_data, "", [], []) + message_service._blast_message.assert_called_once_with( + stream_ids, message, json_data, "", [], [] + ) @pytest.mark.asyncio async def test_blast_complex_message(message_service): - blast_message_response = get_deserialized_object_from_resource(V4MessageBlastResponse, - "message_response/blast_message.json") + blast_message_response = get_deserialized_object_from_resource( + V4MessageBlastResponse, "message_response/blast_message.json" + ) message_service._blast_message = AsyncMock(return_value=blast_message_response) stream_ids = ["sid1", "sid2"] content = "Hello" @@ -201,32 +233,41 @@ async def test_blast_complex_message(message_service): preview = "preview" json_data = json.dumps(data) - message = Message(content=content, data=data, attachments=[(attachment, preview)], version=version) + message = Message( + content=content, data=data, attachments=[(attachment, preview)], version=version + ) await message_service.blast_message(stream_ids, message) - message_service._blast_message.assert_called_once_with(stream_ids, content, json_data, version, [attachment], - [preview]) + message_service._blast_message.assert_called_once_with( + stream_ids, content, json_data, version, [attachment], [preview] + ) @pytest.mark.asyncio async def test_import_message(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = deserialize_object(V4ImportResponseList, - "[" - " {" - " \"messageId\": \"FjSY1y3L\", " - " \"originatingSystemId\": \"AGENT_SDK\"," - " \"originalMessageId\": \"M2\"" - " }" - "]") - - import_response = await message_service.import_messages([V4ImportedMessage( - message="test_message", - intended_message_timestamp=1433045622000, - intended_message_from_user_id=7215545057281, - originating_system_id="fooChat", - stream_id="Z3oQRAZGTCNl5KjiUH2G1n___qr9lLT8dA" - )]) + mocked_api_client.call_api.return_value = deserialize_object( + V4ImportResponseList, + "[" + " {" + ' "messageId": "FjSY1y3L", ' + ' "originatingSystemId": "AGENT_SDK",' + ' "originalMessageId": "M2"' + " }" + "]", + ) + + import_response = await message_service.import_messages( + [ + V4ImportedMessage( + message="test_message", + intended_message_timestamp=1433045622000, + intended_message_from_user_id=7215545057281, + originating_system_id="fooChat", + stream_id="Z3oQRAZGTCNl5KjiUH2G1n___qr9lLT8dA", + ) + ] + ) assert len(import_response) == 1 assert import_response[0].message_id == "FjSY1y3L" @@ -244,8 +285,9 @@ async def test_get_attachment(mocked_api_client, message_service): @pytest.mark.asyncio async def test_suppress_message(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(MessageSuppressionResponse, "message_response/supress_message.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + MessageSuppressionResponse, "message_response/supress_message.json" + ) suppress_response = await message_service.suppress_message("test-message-id") assert suppress_response.message_id == "test-message-id" @@ -254,8 +296,9 @@ async def test_suppress_message(mocked_api_client, message_service): @pytest.mark.asyncio async def test_get_message_status(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(MessageStatus, "message_response/message_status.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + MessageStatus, "message_response/message_status.json" + ) message_status = await message_service.get_message_status("test-message-id") @@ -266,12 +309,9 @@ async def test_get_message_status(mocked_api_client, message_service): @pytest.mark.asyncio async def test_get_attachment_types(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = deserialize_object(StringList, "[" - " \".bmp\"," - " \".doc\"," - " \".png\"," - " \".mpeg\"" - "]") + mocked_api_client.call_api.return_value = deserialize_object( + StringList, '[ ".bmp", ".doc", ".png", ".mpeg"]' + ) attachment_types = await message_service.get_attachment_types() @@ -281,8 +321,9 @@ async def test_get_attachment_types(mocked_api_client, message_service): @pytest.mark.asyncio async def test_get_message(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V4Message, - "message_response/message.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V4Message, "message_response/message.json" + ) message = await message_service.get_message("-AANBHKtUC-2q6_0WSjBGX___pHnSfBKdA") @@ -293,8 +334,9 @@ async def test_get_message(mocked_api_client, message_service): @pytest.mark.asyncio async def test_list_attachments(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(StreamAttachmentResponse, "message_response/list_attachments.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + StreamAttachmentResponse, "message_response/list_attachments.json" + ) attachments = await message_service.list_attachments("stream_id") @@ -305,8 +347,9 @@ async def test_list_attachments(mocked_api_client, message_service): @pytest.mark.asyncio async def test_message_receipts(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(MessageReceiptDetailResponse, "message_response/message_receipts.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + MessageReceiptDetailResponse, "message_response/message_receipts.json" + ) message_receipts = await message_service.list_message_receipts("test-message-id") @@ -317,10 +360,13 @@ async def test_message_receipts(mocked_api_client, message_service): @pytest.mark.asyncio async def test_get_message_relationships(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(MessageMetadataResponse, "message_response/message_relationships.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + MessageMetadataResponse, "message_response/message_relationships.json" + ) - message_relationships = await message_service.get_message_relationships("TYgOZ65dVsu3SeK7u2YdfH///o6fzBu") + message_relationships = await message_service.get_message_relationships( + "TYgOZ65dVsu3SeK7u2YdfH///o6fzBu" + ) assert message_relationships.message_id == "TYgOZ65dVsu3SeK7u2YdfH///o6fzBu" assert message_relationships.parent.message_id == "/rbLQW5UHKZffM0FlLO2rn///o6vTck" @@ -329,8 +375,9 @@ async def test_get_message_relationships(mocked_api_client, message_service): @pytest.mark.asyncio async def test_search_messages_with_hashtag(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(V4MessageList, "message_response/list_messages.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V4MessageList, "message_response/list_messages.json" + ) messages = await message_service.search_messages(MessageSearchQuery(hashtag="tag")) assert len(messages) == 1 @@ -339,9 +386,12 @@ async def test_search_messages_with_hashtag(mocked_api_client, message_service): @pytest.mark.asyncio @pytest.mark.parametrize("stream_type", ["CHAT", "IM", "MIM", "ROOM", "POST"]) -async def test_search_messages_with_valid_stream_type(mocked_api_client, message_service, stream_type): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(V4MessageList, "message_response/list_messages.json") +async def test_search_messages_with_valid_stream_type( + mocked_api_client, message_service, stream_type +): + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V4MessageList, "message_response/list_messages.json" + ) messages = await message_service.search_messages(MessageSearchQuery(stream_type=stream_type)) assert len(messages) == 1 @@ -350,8 +400,9 @@ async def test_search_messages_with_valid_stream_type(mocked_api_client, message @pytest.mark.asyncio async def test_search_messages_with_invalid_stream_type(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(V4MessageList, "message_response/list_messages.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V4MessageList, "message_response/list_messages.json" + ) with pytest.raises(ValueError): await message_service.search_messages(MessageSearchQuery(stream_type="invalid")) @@ -359,18 +410,22 @@ async def test_search_messages_with_invalid_stream_type(mocked_api_client, messa @pytest.mark.asyncio async def test_search_messages_with_text_and_sid(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(V4MessageList, "message_response/list_messages.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V4MessageList, "message_response/list_messages.json" + ) - messages = await message_service.search_messages(MessageSearchQuery(text="some text", stream_id="sid")) + messages = await message_service.search_messages( + MessageSearchQuery(text="some text", stream_id="sid") + ) assert len(messages) == 1 assert messages[0].message_id == "test-message1" @pytest.mark.asyncio async def test_search_messages_with_text_and_no_sid(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(V4MessageList, "message_response/list_messages.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V4MessageList, "message_response/list_messages.json" + ) with pytest.raises(ValueError): await message_service.search_messages(MessageSearchQuery(text="some text")) @@ -378,35 +433,46 @@ async def test_search_messages_with_text_and_no_sid(mocked_api_client, message_s @pytest.mark.asyncio @pytest.mark.parametrize("stream_type", ["CHAT", "IM", "MIM", "ROOM", "POST"]) -async def test_search_messages_with_stream_type_text_and_sid(mocked_api_client, message_service, stream_type): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(V4MessageList, "message_response/list_messages.json") +async def test_search_messages_with_stream_type_text_and_sid( + mocked_api_client, message_service, stream_type +): + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V4MessageList, "message_response/list_messages.json" + ) messages = await message_service.search_messages( - MessageSearchQuery(text="some text", stream_id="sid", stream_type=stream_type)) + MessageSearchQuery(text="some text", stream_id="sid", stream_type=stream_type) + ) assert len(messages) == 1 assert messages[0].message_id == "test-message1" @pytest.mark.asyncio @pytest.mark.parametrize("stream_type", ["CHAT", "IM", "MIM", "ROOM", "POST"]) -async def test_search_messages_with_stream_type_text_and_no_sid(mocked_api_client, message_service, stream_type): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(V4MessageList, "message_response/list_messages.json") +async def test_search_messages_with_stream_type_text_and_no_sid( + mocked_api_client, message_service, stream_type +): + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V4MessageList, "message_response/list_messages.json" + ) with pytest.raises(ValueError): - await message_service.search_messages(MessageSearchQuery(text="some text", stream_type=stream_type)) + await message_service.search_messages( + MessageSearchQuery(text="some text", stream_type=stream_type) + ) @pytest.mark.asyncio async def test_search_all_messages(mocked_api_client, message_service): - mocked_api_client.call_api.side_effect = \ - [get_deserialized_object_from_resource(V4MessageList, "message_response/list_messages.json"), - V4MessageList(value=[])] + mocked_api_client.call_api.side_effect = [ + get_deserialized_object_from_resource(V4MessageList, "message_response/list_messages.json"), + V4MessageList(value=[]), + ] chunk_size = 1 - message_generator = await message_service.search_all_messages(MessageSearchQuery(hashtag="tag"), - chunk_size=chunk_size) + message_generator = await message_service.search_all_messages( + MessageSearchQuery(hashtag="tag"), chunk_size=chunk_size + ) messages = [m async for m in message_generator] assert len(messages) == 1 assert messages[0].message_id == "test-message1" @@ -416,9 +482,12 @@ async def test_search_all_messages(mocked_api_client, message_service): @pytest.mark.asyncio async def test_update_message(mocked_api_client, message_service): - mocked_api_client.call_api.return_value = \ - get_deserialized_object_from_resource(V4Message, "message_response/update_message.json") - message = await message_service.update_message("stream_id", "message_id", "test_message", "data", "version") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V4Message, "message_response/update_message.json" + ) + message = await message_service.update_message( + "stream_id", "message_id", "test_message", "data", "version" + ) assert message.message_id == "ikCBeVCgQT876veVzQzOV3___oNI6f8obQ" assert message.user.user_id == 11338713662703 diff --git a/tests/core/service/message/model_test.py b/tests/core/service/message/model_test.py index f4fc0f25..98c9f85b 100644 --- a/tests/core/service/message/model_test.py +++ b/tests/core/service/message/model_test.py @@ -6,8 +6,15 @@ from symphony.bdk.core.service.message.model import Message -def assert_message_properties_equal(actual_message, expected_content, expected_data, expected_silent, expected_version, - expected_attachments, expected_previews): +def assert_message_properties_equal( + actual_message, + expected_content, + expected_data, + expected_silent, + expected_version, + expected_attachments, + expected_previews, +): assert actual_message.content == expected_content assert actual_message.data == expected_data assert actual_message.silent == expected_silent @@ -22,15 +29,15 @@ def test_create_message_with_no_content(): def test_create_message_content_only_without_tags(): - assert_message_properties_equal(Message(content="Hello world!"), - "Hello world!", "", True, "", [], []) + assert_message_properties_equal( + Message(content="Hello world!"), "Hello world!", "", True, "", [], [] + ) def test_create_message_content_only(): content = "Hello world!" - assert_message_properties_equal(Message(content=content), - content, "", True, "", [], []) + assert_message_properties_equal(Message(content=content), content, "", True, "", [], []) def test_create_message_content_data_version(): @@ -39,44 +46,70 @@ def test_create_message_content_data_version(): data = ["foo", {"bar": ("baz", 1.0, 2)}] json_data = json.dumps(data) - assert_message_properties_equal(Message(content=content, version=version, data=data), - content, json_data, True, version, [], []) + assert_message_properties_equal( + Message(content=content, version=version, data=data), + content, + json_data, + True, + version, + [], + [], + ) def test_create_message_with_attachment(): content = "Hello world!" attachments = ["some attachment"] - assert_message_properties_equal(Message(content=content, attachments=attachments), - content, "", True, "", attachments, []) + assert_message_properties_equal( + Message(content=content, attachments=attachments), content, "", True, "", attachments, [] + ) def test_create_message_with_attachment_one_element_tuple(): content = "Hello world!" attachment = "some attachment" - assert_message_properties_equal(Message(content=content, attachments=[(attachment,)]), - content, "", True, "", [attachment], []) + assert_message_properties_equal( + Message(content=content, attachments=[(attachment,)]), + content, + "", + True, + "", + [attachment], + [], + ) def test_create_message_with_attachment_and_preview(): content = "Hello world!" attachment = "some attachment" preview = "some preview" - assert_message_properties_equal(Message(content=content, attachments=[(attachment, preview)]), - content, "", True, "", [attachment], [preview]) + assert_message_properties_equal( + Message(content=content, attachments=[(attachment, preview)]), + content, + "", + True, + "", + [attachment], + [preview], + ) def test_create_message_with_attachment_and_no_preview_for_second_attachment(): with pytest.raises(MessageCreationError): - Message(content="Hello world!", - attachments=[("first attachment", "first preview"), "second attachment"]) + Message( + content="Hello world!", + attachments=[("first attachment", "first preview"), "second attachment"], + ) def test_create_message_with_attachment_and_no_preview_for_first_attachment(): with pytest.raises(MessageCreationError): - Message(content="Hello world!", - attachments=["first attachment", ("second attachment", "second preview")]) + Message( + content="Hello world!", + attachments=["first attachment", ("second attachment", "second preview")], + ) def test_create_message_content_data_version_silent(): @@ -86,5 +119,12 @@ def test_create_message_content_data_version_silent(): silent = False json_data = json.dumps(data) - assert_message_properties_equal(Message(content=content, version=version, data=data, silent=silent), - content, json_data, silent, version, [], []) + assert_message_properties_equal( + Message(content=content, version=version, data=data, silent=silent), + content, + json_data, + silent, + version, + [], + [], + ) diff --git a/tests/core/service/pagination_test.py b/tests/core/service/pagination_test.py index 3ce8ca12..babe8000 100644 --- a/tests/core/service/pagination_test.py +++ b/tests/core/service/pagination_test.py @@ -2,7 +2,7 @@ import pytest -from symphony.bdk.core.service.pagination import offset_based_pagination, cursor_based_pagination +from symphony.bdk.core.service.pagination import cursor_based_pagination, offset_based_pagination AFTER = "after" @@ -10,91 +10,126 @@ class TestOffsetBasedPagination: - @staticmethod - async def assert_generator_produces(func_responses, max_number, expected_output, expected_calls): + async def assert_generator_produces( + func_responses, max_number, expected_output, expected_calls + ): mock_func = AsyncMock() mock_func.side_effect = func_responses - assert [x async for x in offset_based_pagination(mock_func, CHUNK_SIZE, max_number)] == expected_output + assert [ + x async for x in offset_based_pagination(mock_func, CHUNK_SIZE, max_number) + ] == expected_output mock_func.assert_has_awaits(expected_calls) @pytest.mark.asyncio async def test_empty_answer(self): - await self.assert_generator_produces(func_responses=[[]], max_number=None, - expected_output=[], - expected_calls=[call(0, CHUNK_SIZE)]) + await self.assert_generator_produces( + func_responses=[[]], + max_number=None, + expected_output=[], + expected_calls=[call(0, CHUNK_SIZE)], + ) @pytest.mark.asyncio async def test_answer_less_than_one_chunk(self): - await self.assert_generator_produces(func_responses=[["one"]], max_number=None, - expected_output=["one"], - expected_calls=[call(0, CHUNK_SIZE)]) + await self.assert_generator_produces( + func_responses=[["one"]], + max_number=None, + expected_output=["one"], + expected_calls=[call(0, CHUNK_SIZE)], + ) @pytest.mark.asyncio async def test_answer_same_length_than_one_chunk(self): - await self.assert_generator_produces(func_responses=[["one", "two"], []], max_number=None, - expected_output=["one", "two"], - expected_calls=[call(0, CHUNK_SIZE), call(CHUNK_SIZE, CHUNK_SIZE)]) + await self.assert_generator_produces( + func_responses=[["one", "two"], []], + max_number=None, + expected_output=["one", "two"], + expected_calls=[call(0, CHUNK_SIZE), call(CHUNK_SIZE, CHUNK_SIZE)], + ) @pytest.mark.asyncio async def test_answer_more_than_one_chunk_less_than_two_chunks(self): - await self.assert_generator_produces(func_responses=[["one", "two"], ["three"]], max_number=None, - expected_output=["one", "two", "three"], - expected_calls=[call(0, CHUNK_SIZE), call(CHUNK_SIZE, CHUNK_SIZE)]) + await self.assert_generator_produces( + func_responses=[["one", "two"], ["three"]], + max_number=None, + expected_output=["one", "two", "three"], + expected_calls=[call(0, CHUNK_SIZE), call(CHUNK_SIZE, CHUNK_SIZE)], + ) @pytest.mark.asyncio async def test_answer_two_chunks(self): - await self.assert_generator_produces(func_responses=[["one", "two"], ["three", "four"], []], max_number=None, - expected_output=["one", "two", "three", "four"], - expected_calls=[call(0, CHUNK_SIZE), call(CHUNK_SIZE, CHUNK_SIZE), - call(2 * CHUNK_SIZE, CHUNK_SIZE)]) + await self.assert_generator_produces( + func_responses=[["one", "two"], ["three", "four"], []], + max_number=None, + expected_output=["one", "two", "three", "four"], + expected_calls=[ + call(0, CHUNK_SIZE), + call(CHUNK_SIZE, CHUNK_SIZE), + call(2 * CHUNK_SIZE, CHUNK_SIZE), + ], + ) @pytest.mark.asyncio async def test_negative_max_number(self): - await self.assert_generator_produces(func_responses=[[]], max_number=-1, - expected_output=[], - expected_calls=[]) + await self.assert_generator_produces( + func_responses=[[]], max_number=-1, expected_output=[], expected_calls=[] + ) @pytest.mark.asyncio async def test_zero_max_number(self): - await self.assert_generator_produces(func_responses=[[]], max_number=0, - expected_output=[], - expected_calls=[]) + await self.assert_generator_produces( + func_responses=[[]], max_number=0, expected_output=[], expected_calls=[] + ) @pytest.mark.asyncio async def test_max_number_less_than_one_chunk(self): - await self.assert_generator_produces(func_responses=[["one", "two"]], max_number=1, - expected_output=["one"], - expected_calls=[call(0, CHUNK_SIZE)]) + await self.assert_generator_produces( + func_responses=[["one", "two"]], + max_number=1, + expected_output=["one"], + expected_calls=[call(0, CHUNK_SIZE)], + ) @pytest.mark.asyncio async def test_max_number_equals_one_chunk(self): - await self.assert_generator_produces(func_responses=[["one", "two"]], max_number=CHUNK_SIZE, - expected_output=["one", "two"], - expected_calls=[call(0, CHUNK_SIZE)]) + await self.assert_generator_produces( + func_responses=[["one", "two"]], + max_number=CHUNK_SIZE, + expected_output=["one", "two"], + expected_calls=[call(0, CHUNK_SIZE)], + ) @pytest.mark.asyncio async def test_max_number_equals_more_than_one_chunk(self): - await self.assert_generator_produces(func_responses=[["one", "two"], ["three", "four"]], max_number=3, - expected_output=["one", "two", "three"], - expected_calls=[call(0, CHUNK_SIZE), call(CHUNK_SIZE, CHUNK_SIZE)]) + await self.assert_generator_produces( + func_responses=[["one", "two"], ["three", "four"]], + max_number=3, + expected_output=["one", "two", "three"], + expected_calls=[call(0, CHUNK_SIZE), call(CHUNK_SIZE, CHUNK_SIZE)], + ) @pytest.mark.asyncio async def test_func_returns_none(self): - await self.assert_generator_produces(func_responses=[None], max_number=None, - expected_output=[], - expected_calls=[call(0, CHUNK_SIZE)]) + await self.assert_generator_produces( + func_responses=[None], + max_number=None, + expected_output=[], + expected_calls=[call(0, CHUNK_SIZE)], + ) @pytest.mark.asyncio async def test_func_second_chunk_returns_none(self): - await self.assert_generator_produces(func_responses=[["one", "two"], None], max_number=None, - expected_output=["one", "two"], - expected_calls=[call(0, CHUNK_SIZE), call(CHUNK_SIZE, CHUNK_SIZE)]) + await self.assert_generator_produces( + func_responses=[["one", "two"], None], + max_number=None, + expected_output=["one", "two"], + expected_calls=[call(0, CHUNK_SIZE), call(CHUNK_SIZE, CHUNK_SIZE)], + ) class TestCursorBasedPagination: - @pytest.mark.asyncio async def test_answer_none(self): mock_func = AsyncMock() @@ -124,7 +159,11 @@ async def test_answer_two_chunks(self): mock_func = AsyncMock() mock_func.side_effect = [(["one", "two"], AFTER), (["three"], None)] - assert [x async for x in cursor_based_pagination(mock_func, CHUNK_SIZE)] == ["one", "two", "three"] + assert [x async for x in cursor_based_pagination(mock_func, CHUNK_SIZE)] == [ + "one", + "two", + "three", + ] mock_func.assert_has_awaits([call(CHUNK_SIZE, None), call(CHUNK_SIZE, AFTER)]) @pytest.mark.asyncio @@ -154,7 +193,10 @@ async def test_max_number_equals_one_chunk(self): mock_func = AsyncMock() mock_func.side_effect = [(["one", "two"], AFTER)] - assert [x async for x in cursor_based_pagination(mock_func, CHUNK_SIZE, 2)] == ["one", "two"] + assert [x async for x in cursor_based_pagination(mock_func, CHUNK_SIZE, 2)] == [ + "one", + "two", + ] mock_func.assert_has_awaits([call(CHUNK_SIZE, None)]) @pytest.mark.asyncio @@ -162,5 +204,9 @@ async def test_max_number_equals_more_than_one_chunk(self): mock_func = AsyncMock() mock_func.side_effect = [(["one", "two"], AFTER), (["three", "four"], "after_two")] - assert [x async for x in cursor_based_pagination(mock_func, CHUNK_SIZE, 3)] == ["one", "two", "three"] + assert [x async for x in cursor_based_pagination(mock_func, CHUNK_SIZE, 3)] == [ + "one", + "two", + "three", + ] mock_func.assert_has_awaits([call(CHUNK_SIZE, None), call(CHUNK_SIZE, AFTER)]) diff --git a/tests/core/service/presence/presence_service_test.py b/tests/core/service/presence/presence_service_test.py index fe740c6c..2ec98cc2 100644 --- a/tests/core/service/presence/presence_service_test.py +++ b/tests/core/service/presence/presence_service_test.py @@ -1,18 +1,18 @@ -from unittest.mock import MagicMock, AsyncMock +from unittest.mock import AsyncMock, MagicMock + import pytest -from symphony.bdk.gen import ApiException -from symphony.bdk.gen.pod_api.presence_api import PresenceApi from symphony.bdk.core.auth.auth_session import AuthSession from symphony.bdk.core.service.presence.presence_service import PresenceService, PresenceStatus +from symphony.bdk.gen import ApiException +from symphony.bdk.gen.pod_api.presence_api import PresenceApi from symphony.bdk.gen.pod_model.string_id import StringId from symphony.bdk.gen.pod_model.v2_presence import V2Presence from symphony.bdk.gen.pod_model.v2_presence_list import V2PresenceList from symphony.bdk.gen.pod_model.v2_presence_status import V2PresenceStatus from symphony.bdk.gen.pod_model.v2_user_presence import V2UserPresence - -from tests.utils.resource_utils import deserialize_object from tests.core.config import minimal_retry_config +from tests.utils.resource_utils import deserialize_object @pytest.fixture(name="auth_session") @@ -45,10 +45,9 @@ def fixture_presence_service(mocked_presence_api_client, auth_session): @pytest.mark.asyncio async def test_get_presence(presence_service, mocked_presence_api_client): - mocked_presence_api_client.v2_user_presence_get.return_value = deserialize_object(V2Presence, - "{\"category\":\"AVAILABLE\"," - "\"userId\":14568529068038," - "\"timestamp\":1533928483800}") + mocked_presence_api_client.v2_user_presence_get.return_value = deserialize_object( + V2Presence, '{"category":"AVAILABLE","userId":14568529068038,"timestamp":1533928483800}' + ) presence = await presence_service.get_presence() @@ -66,24 +65,26 @@ async def test_get_presence_failed(presence_service, mocked_presence_api_client) @pytest.mark.asyncio async def test_get_all_presence(presence_service, mocked_presence_api_client): - mocked_presence_api_client.v2_users_presence_get.return_value = \ - deserialize_object(V2PresenceList, "[" - " {" - " \"category\":\"AVAILABLE\"," - " \"userId\":14568529068038," - " \"timestamp\":1533928483800}," - " {" - " \"category\":\"OFFLINE\"," - " \"userId\":974217539631," - " \"timestamp\":1503286226030" - " }" - "]") + mocked_presence_api_client.v2_users_presence_get.return_value = deserialize_object( + V2PresenceList, + "[" + " {" + ' "category":"AVAILABLE",' + ' "userId":14568529068038,' + ' "timestamp":1533928483800},' + " {" + ' "category":"OFFLINE",' + ' "userId":974217539631,' + ' "timestamp":1503286226030' + " }" + "]", + ) presence_list = await presence_service.get_all_presence(1234, 5000) - mocked_presence_api_client.v2_users_presence_get.assert_called_once_with(session_token="session_token", - last_user_id=1234, - limit=5000) + mocked_presence_api_client.v2_users_presence_get.assert_called_once_with( + session_token="session_token", last_user_id=1234, limit=5000 + ) assert len(presence_list) == 2 assert presence_list[0].user_id == 14568529068038 assert presence_list[0].category == "AVAILABLE" @@ -102,17 +103,15 @@ async def test_get_all_presence_failed(presence_service, mocked_presence_api_cli @pytest.mark.asyncio async def test_get_user_presence(presence_service, mocked_presence_api_client): - mocked_presence_api_client.v3_user_uid_presence_get.return_value = \ - deserialize_object(V2Presence, - "{\"category\":\"AVAILABLE\"," - "\"userId\":14568529068038," - "\"timestamp\":1533928483800}") + mocked_presence_api_client.v3_user_uid_presence_get.return_value = deserialize_object( + V2Presence, '{"category":"AVAILABLE","userId":14568529068038,"timestamp":1533928483800}' + ) presence = await presence_service.get_user_presence(1234, True) - mocked_presence_api_client.v3_user_uid_presence_get.assert_called_once_with(uid=1234, - session_token="session_token", - local=True) + mocked_presence_api_client.v3_user_uid_presence_get.assert_called_once_with( + uid=1234, session_token="session_token", local=True + ) assert presence.user_id == 14568529068038 assert presence.category == "AVAILABLE" @@ -129,8 +128,9 @@ async def test_get_user_presence_failed(presence_service, mocked_presence_api_cl async def test_external_presence_interest(presence_service, mocked_presence_api_client): uid_list = [1234] await presence_service.external_presence_interest(uid_list) - mocked_presence_api_client.v1_user_presence_register_post.assert_called_once_with(session_token="session_token", - uid_list=uid_list) + mocked_presence_api_client.v1_user_presence_register_post.assert_called_once_with( + session_token="session_token", uid_list=uid_list + ) @pytest.mark.asyncio @@ -143,16 +143,15 @@ async def test_external_presence_interest_failed(presence_service, mocked_presen @pytest.mark.asyncio async def test_set_presence(presence_service, mocked_presence_api_client): - mocked_presence_api_client.v2_user_presence_post.return_value = deserialize_object(V2Presence, - "{\"category\":\"AWAY\"," - "\"userId\":14568529068038," - "\"timestamp\":1533928483800}") + mocked_presence_api_client.v2_user_presence_post.return_value = deserialize_object( + V2Presence, '{"category":"AWAY","userId":14568529068038,"timestamp":1533928483800}' + ) presence = await presence_service.set_presence(PresenceStatus.AWAY, True) - mocked_presence_api_client.v2_user_presence_post.assert_called_once_with(session_token="session_token", - presence=V2PresenceStatus(category="AWAY"), - soft=True) + mocked_presence_api_client.v2_user_presence_post.assert_called_once_with( + session_token="session_token", presence=V2PresenceStatus(category="AWAY"), soft=True + ) assert presence.category == "AWAY" assert presence.user_id == 14568529068038 @@ -167,8 +166,9 @@ async def test_set_presence_failed(presence_service, mocked_presence_api_client) @pytest.mark.asyncio async def test_create_presence_feed(presence_service, mocked_presence_api_client): - mocked_presence_api_client.v1_presence_feed_create_post.return_value = \ - deserialize_object(StringId, "{\"id\":\"c4dca251-8639-48db-a9d4-f387089e17cf\"}") + mocked_presence_api_client.v1_presence_feed_create_post.return_value = deserialize_object( + StringId, '{"id":"c4dca251-8639-48db-a9d4-f387089e17cf"}' + ) feed_id = await presence_service.create_presence_feed() @@ -185,25 +185,28 @@ async def test_create_presence_feed_failed(presence_service, mocked_presence_api @pytest.mark.asyncio async def test_read_presence_feed(presence_service, mocked_presence_api_client): - mocked_presence_api_client.v1_presence_feed_feed_id_read_get.return_value = \ - deserialize_object(V2PresenceList, "[" - " {" - " \"category\":\"AVAILABLE\"," - " \"userId\":7078106103901," - " \"timestamp\":1489769156271" - " }," - " {" - " \"category\":\"ON_THE_PHONE\"," - " \"userId\":7078106103902," - " \"timestamp\":1489769156273" - " }" - "]") + mocked_presence_api_client.v1_presence_feed_feed_id_read_get.return_value = deserialize_object( + V2PresenceList, + "[" + " {" + ' "category":"AVAILABLE",' + ' "userId":7078106103901,' + ' "timestamp":1489769156271' + " }," + " {" + ' "category":"ON_THE_PHONE",' + ' "userId":7078106103902,' + ' "timestamp":1489769156273' + " }" + "]", + ) feed_id = "c4dca251-8639-48db-a9d4-f387089e17cf" presence_list = await presence_service.read_presence_feed(feed_id) - mocked_presence_api_client.v1_presence_feed_feed_id_read_get.assert_called_once_with(session_token="session_token", - feed_id=feed_id) + mocked_presence_api_client.v1_presence_feed_feed_id_read_get.assert_called_once_with( + session_token="session_token", feed_id=feed_id + ) assert len(presence_list) == 2 assert presence_list[0].category == "AVAILABLE" assert presence_list[0].user_id == 7078106103901 @@ -221,14 +224,15 @@ async def test_read_presence_feed_failed(presence_service, mocked_presence_api_c @pytest.mark.asyncio async def test_delete_presence_feed(presence_service, mocked_presence_api_client): - mocked_presence_api_client.v1_presence_feed_feed_id_delete_post.return_value = \ - deserialize_object(StringId, "{\"id\":\"c4dca251-8639-48db-a9d4-f387089e17cf\"}") + mocked_presence_api_client.v1_presence_feed_feed_id_delete_post.return_value = ( + deserialize_object(StringId, '{"id":"c4dca251-8639-48db-a9d4-f387089e17cf"}') + ) feed_id = await presence_service.delete_presence_feed("c4dca251-8639-48db-a9d4-f387089e17cf") mocked_presence_api_client.v1_presence_feed_feed_id_delete_post.assert_called_once_with( - session_token="session_token", - feed_id=feed_id) + session_token="session_token", feed_id=feed_id + ) assert feed_id == "c4dca251-8639-48db-a9d4-f387089e17cf" @@ -242,18 +246,17 @@ async def test_delete_presence_feed_failed(presence_service, mocked_presence_api @pytest.mark.asyncio async def test_set_user_presence(presence_service, mocked_presence_api_client): - mocked_presence_api_client.v3_user_presence_post.return_value = deserialize_object(V2Presence, - "{\"category\":\"AWAY\"," - "\"userId\":14568529068038," - "\"timestamp\":1533928483800}") + mocked_presence_api_client.v3_user_presence_post.return_value = deserialize_object( + V2Presence, '{"category":"AWAY","userId":14568529068038,"timestamp":1533928483800}' + ) presence = await presence_service.set_user_presence(14568529068038, PresenceStatus.AWAY, True) - mocked_presence_api_client.v3_user_presence_post.assert_called_once_with(session_token="session_token", - presence=V2UserPresence( - user_id=14568529068038, - category="AWAY"), - soft=True) + mocked_presence_api_client.v3_user_presence_post.assert_called_once_with( + session_token="session_token", + presence=V2UserPresence(user_id=14568529068038, category="AWAY"), + soft=True, + ) assert presence.user_id == 14568529068038 assert presence.category == "AWAY" diff --git a/tests/core/service/session/session_service_test.py b/tests/core/service/session/session_service_test.py index 584f7cd4..9043b53f 100644 --- a/tests/core/service/session/session_service_test.py +++ b/tests/core/service/session/session_service_test.py @@ -1,4 +1,4 @@ -from unittest.mock import MagicMock, AsyncMock +from unittest.mock import AsyncMock, MagicMock import pytest @@ -6,8 +6,8 @@ from symphony.bdk.core.service.session.session_service import SessionService from symphony.bdk.gen.pod_api.session_api import SessionApi from symphony.bdk.gen.pod_model.user_v2 import UserV2 -from tests.utils.resource_utils import get_deserialized_object_from_resource from tests.core.config import minimal_retry_config +from tests.utils.resource_utils import get_deserialized_object_from_resource @pytest.fixture(name="auth_session") @@ -25,19 +25,16 @@ def fixture_session_api(): @pytest.fixture(name="session_service") def fixture_session_service(session_api, auth_session): - service = SessionService( - session_api, - auth_session, - minimal_retry_config() - ) + service = SessionService(session_api, auth_session, minimal_retry_config()) return service @pytest.mark.asyncio async def test_get_session(session_api, session_service): session_api.v2_sessioninfo_get = AsyncMock() - session_api.v2_sessioninfo_get.return_value = get_deserialized_object_from_resource(UserV2, - "session/get_session.json") + session_api.v2_sessioninfo_get.return_value = get_deserialized_object_from_resource( + UserV2, "session/get_session.json" + ) session = await session_service.get_session() assert session.display_name == "Symphony Admin" diff --git a/tests/core/service/signal/signal_service_test.py b/tests/core/service/signal/signal_service_test.py index 5afb065b..b69e176e 100644 --- a/tests/core/service/signal/signal_service_test.py +++ b/tests/core/service/signal/signal_service_test.py @@ -1,4 +1,4 @@ -from unittest.mock import MagicMock, AsyncMock +from unittest.mock import AsyncMock, MagicMock import pytest @@ -36,17 +36,15 @@ def fixture_signal_service(signals_api, auth_session): @pytest.mark.asyncio async def test_list_signals(signals_api, signal_service): signals_api.v1_signals_list_get = AsyncMock() - signals_api.v1_signals_list_get.return_value = get_deserialized_object_from_resource(SignalList, - "signal/list_signals.json") + signals_api.v1_signals_list_get.return_value = get_deserialized_object_from_resource( + SignalList, "signal/list_signals.json" + ) signals = await signal_service.list_signals() signal_list = signals.value signals_api.v1_signals_list_get.assert_called_with( - skip=0, - limit=50, - session_token="session_token", - key_manager_token="km_token" + skip=0, limit=50, session_token="session_token", key_manager_token="km_token" ) assert len(signal_list) == 2 @@ -56,17 +54,15 @@ async def test_list_signals(signals_api, signal_service): @pytest.mark.asyncio async def test_list_all_signals(signals_api, signal_service): signals_api.v1_signals_list_get = AsyncMock() - signals_api.v1_signals_list_get.return_value = get_deserialized_object_from_resource(SignalList, - "signal/list_signals.json") + signals_api.v1_signals_list_get.return_value = get_deserialized_object_from_resource( + SignalList, "signal/list_signals.json" + ) signal_list_gen = await signal_service.list_all_signals() signal_list = [s async for s in signal_list_gen] signals_api.v1_signals_list_get.assert_called_with( - skip=0, - limit=50, - session_token="session_token", - key_manager_token="km_token" + skip=0, limit=50, session_token="session_token", key_manager_token="km_token" ) assert len(signal_list) == 2 @@ -76,17 +72,15 @@ async def test_list_all_signals(signals_api, signal_service): @pytest.mark.asyncio async def test_list_signals_with_skip_and_limit(signals_api, signal_service): signals_api.v1_signals_list_get = AsyncMock() - signals_api.v1_signals_list_get.return_value = \ - get_deserialized_object_from_resource(SignalList, "signal/list_signals.json") + signals_api.v1_signals_list_get.return_value = get_deserialized_object_from_resource( + SignalList, "signal/list_signals.json" + ) signal_list = await signal_service.list_signals(3, 30) signal_list = signal_list.value signals_api.v1_signals_list_get.assert_called_with( - skip=3, - limit=30, - session_token="session_token", - key_manager_token="km_token" + skip=3, limit=30, session_token="session_token", key_manager_token="km_token" ) assert len(signal_list) == 2 @@ -96,15 +90,14 @@ async def test_list_signals_with_skip_and_limit(signals_api, signal_service): @pytest.mark.asyncio async def test_get_signal(signals_api, signal_service): signals_api.v1_signals_id_get_get = AsyncMock() - signals_api.v1_signals_id_get_get.return_value = \ - get_deserialized_object_from_resource(Signal, "signal/create_signal.json") + signals_api.v1_signals_id_get_get.return_value = get_deserialized_object_from_resource( + Signal, "signal/create_signal.json" + ) signal = await signal_service.get_signal("signal_id") signals_api.v1_signals_id_get_get.assert_called_with( - id="signal_id", - session_token="session_token", - key_manager_token="km_token" + id="signal_id", session_token="session_token", key_manager_token="km_token" ) assert signal.id == "signal_id" @@ -115,15 +108,14 @@ async def test_get_signal(signals_api, signal_service): @pytest.mark.asyncio async def test_create_signal(signals_api, signal_service): signals_api.v1_signals_create_post = AsyncMock() - signals_api.v1_signals_create_post.return_value = \ - get_deserialized_object_from_resource(Signal, "signal/create_signal.json") + signals_api.v1_signals_create_post.return_value = get_deserialized_object_from_resource( + Signal, "signal/create_signal.json" + ) signal = await signal_service.create_signal(BaseSignal()) signals_api.v1_signals_create_post.assert_called_with( - signal=BaseSignal(), - session_token="session_token", - key_manager_token="km_token" + signal=BaseSignal(), session_token="session_token", key_manager_token="km_token" ) assert signal.id == "signal_id" assert signal.name == "hash and cash" @@ -133,8 +125,9 @@ async def test_create_signal(signals_api, signal_service): @pytest.mark.asyncio async def test_update_signal(signals_api, signal_service): signals_api.v1_signals_id_update_post = AsyncMock() - signals_api.v1_signals_id_update_post.return_value = \ - get_deserialized_object_from_resource(Signal, "signal/update_signal.json") + signals_api.v1_signals_id_update_post.return_value = get_deserialized_object_from_resource( + Signal, "signal/update_signal.json" + ) signal = await signal_service.update_signal("signal_id", BaseSignal()) @@ -142,7 +135,7 @@ async def test_update_signal(signals_api, signal_service): id="signal_id", signal=BaseSignal(), session_token="session_token", - key_manager_token="km_token" + key_manager_token="km_token", ) assert signal.id == "signal_id" @@ -156,28 +149,28 @@ async def test_delete_signal(signals_api, signal_service): await signal_service.delete_signal("signal_id") signals_api.v1_signals_id_delete_post.assert_called_with( - id="signal_id", - session_token="session_token", - key_manager_token="km_token" + id="signal_id", session_token="session_token", key_manager_token="km_token" ) @pytest.mark.asyncio async def test_subscribe_users_to_signal(signals_api, signal_service): signals_api.v1_signals_id_subscribe_post = AsyncMock() - signals_api.v1_signals_id_subscribe_post.return_value = \ - get_deserialized_object_from_resource(ChannelSubscriptionResponse, "signal/subscribe_signal.json") + signals_api.v1_signals_id_subscribe_post.return_value = get_deserialized_object_from_resource( + ChannelSubscriptionResponse, "signal/subscribe_signal.json" + ) user_ids = [123, 465, 789] channel_subscription_response = await signal_service.subscribe_users_to_signal( - "signal_id", True, user_ids) + "signal_id", True, user_ids + ) signals_api.v1_signals_id_subscribe_post.assert_called_with( id="signal_id", pushed=True, users=user_ids, session_token="session_token", - key_manager_token="km_token" + key_manager_token="km_token", ) assert channel_subscription_response.requested_subscription == 3 @@ -187,18 +180,17 @@ async def test_subscribe_users_to_signal(signals_api, signal_service): @pytest.mark.asyncio async def test_unsubscribe_users_to_signal(signals_api, signal_service): signals_api.v1_signals_id_unsubscribe_post = AsyncMock() - signals_api.v1_signals_id_unsubscribe_post.return_value = \ - get_deserialized_object_from_resource(ChannelSubscriptionResponse, "signal/subscribe_signal.json") + signals_api.v1_signals_id_unsubscribe_post.return_value = get_deserialized_object_from_resource( + ChannelSubscriptionResponse, "signal/subscribe_signal.json" + ) user_ids = [123, 465, 789] channel_subscription_response = await signal_service.unsubscribe_users_to_signal( - "signal_id", user_ids) + "signal_id", user_ids + ) signals_api.v1_signals_id_unsubscribe_post.assert_called_with( - id="signal_id", - users=user_ids, - session_token="session_token", - key_manager_token="km_token" + id="signal_id", users=user_ids, session_token="session_token", key_manager_token="km_token" ) assert channel_subscription_response.requested_subscription == 3 @@ -208,8 +200,9 @@ async def test_unsubscribe_users_to_signal(signals_api, signal_service): @pytest.mark.asyncio async def test_list_subscribers(signals_api, signal_service): signals_api.v1_signals_id_subscribers_get = AsyncMock() - signals_api.v1_signals_id_subscribers_get.return_value = \ - get_deserialized_object_from_resource(ChannelSubscriberResponse, "signal/list_subscribers.json") + signals_api.v1_signals_id_subscribers_get.return_value = get_deserialized_object_from_resource( + ChannelSubscriberResponse, "signal/list_subscribers.json" + ) channel_subscribers = await signal_service.list_subscribers("signal_id") @@ -218,7 +211,7 @@ async def test_list_subscribers(signals_api, signal_service): skip=0, limit=50, session_token="session_token", - key_manager_token="km_token" + key_manager_token="km_token", ) assert channel_subscribers.total == 150 @@ -228,8 +221,9 @@ async def test_list_subscribers(signals_api, signal_service): @pytest.mark.asyncio async def test_list_subscribers_with_skip_and_limit(signals_api, signal_service): signals_api.v1_signals_id_subscribers_get = AsyncMock() - signals_api.v1_signals_id_subscribers_get.return_value = \ - get_deserialized_object_from_resource(ChannelSubscriberResponse, "signal/list_subscribers.json") + signals_api.v1_signals_id_subscribers_get.return_value = get_deserialized_object_from_resource( + ChannelSubscriberResponse, "signal/list_subscribers.json" + ) channel_subscribers = await signal_service.list_subscribers("signal_id", 1, 10) @@ -238,7 +232,7 @@ async def test_list_subscribers_with_skip_and_limit(signals_api, signal_service) skip=1, limit=10, session_token="session_token", - key_manager_token="km_token" + key_manager_token="km_token", ) assert channel_subscribers.total == 150 @@ -248,8 +242,9 @@ async def test_list_subscribers_with_skip_and_limit(signals_api, signal_service) @pytest.mark.asyncio async def test_list_all_subscribers(signals_api, signal_service): signals_api.v1_signals_id_subscribers_get = AsyncMock() - signals_api.v1_signals_id_subscribers_get.return_value = \ - get_deserialized_object_from_resource(ChannelSubscriberResponse, "signal/list_subscribers.json") + signals_api.v1_signals_id_subscribers_get.return_value = get_deserialized_object_from_resource( + ChannelSubscriberResponse, "signal/list_subscribers.json" + ) channel_subscribers_gen = await signal_service.list_all_subscribers("signal_id", chunk_size=10) subscribers = [s async for s in channel_subscribers_gen] @@ -259,7 +254,7 @@ async def test_list_all_subscribers(signals_api, signal_service): skip=0, limit=10, session_token="session_token", - key_manager_token="km_token" + key_manager_token="km_token", ) assert len(subscribers) == 3 diff --git a/tests/core/service/stream/stream_service_test.py b/tests/core/service/stream/stream_service_test.py index 9fec764a..d9ae96d9 100644 --- a/tests/core/service/stream/stream_service_test.py +++ b/tests/core/service/stream/stream_service_test.py @@ -1,4 +1,4 @@ -from unittest.mock import MagicMock, AsyncMock, Mock +from unittest.mock import AsyncMock, MagicMock, Mock import pytest @@ -67,18 +67,27 @@ def fixture_share_api(mocked_api_client): @pytest.fixture(name="stream_service") def fixture_stream_service(streams_api, room_membership_api, share_api, auth_session): - return StreamService(streams_api, room_membership_api, share_api, auth_session, minimal_retry_config()) + return StreamService( + streams_api, + room_membership_api, + share_api, + auth_session, + minimal_retry_config(), + ) @pytest.mark.asyncio async def test_get_stream(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V2StreamAttributes, - "stream/get_stream.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V2StreamAttributes, "stream/get_stream.json" + ) stream_id = "stream_id" stream_attributes = await stream_service.get_stream(stream_id) - streams_api.v2_streams_sid_info_get.assert_called_once_with(sid=stream_id, session_token=SESSION_TOKEN) + streams_api.v2_streams_sid_info_get.assert_called_once_with( + sid=stream_id, session_token=SESSION_TOKEN + ) assert stream_attributes.id == "ubaSiuUsc_j-_lVQ8vhAz3___opSJdJZdA" assert stream_attributes.room_attributes.name == "New room name" assert stream_attributes.stream_type.type == "ROOM" @@ -86,8 +95,9 @@ async def test_get_stream(mocked_api_client, stream_service, streams_api): @pytest.mark.asyncio async def test_list_streams(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(StreamList, - "stream/list_streams.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + StreamList, "stream/list_streams.json" + ) stream_filter = StreamFilter() skip = 1 @@ -95,8 +105,9 @@ async def test_list_streams(mocked_api_client, stream_service, streams_api): streams = await stream_service.list_streams(stream_filter, skip, limit) - streams_api.v1_streams_list_post.assert_called_once_with(filter=stream_filter, skip=skip, limit=limit, - session_token=SESSION_TOKEN) + streams_api.v1_streams_list_post.assert_called_once_with( + filter=stream_filter, skip=skip, limit=limit, session_token=SESSION_TOKEN + ) assert len(streams.value) == 2 assert streams.value[0].id == "ouAS1QXEHtIhXdZq4NzJBX___oscpQFcdA" assert streams.value[0].room_attributes.name == "Room APP-3033" @@ -106,8 +117,9 @@ async def test_list_streams(mocked_api_client, stream_service, streams_api): @pytest.mark.asyncio async def test_list_all_streams(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(StreamList, - "stream/list_streams.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + StreamList, "stream/list_streams.json" + ) stream_filter = StreamFilter() limit = 4 @@ -115,8 +127,9 @@ async def test_list_all_streams(mocked_api_client, stream_service, streams_api): gen = await stream_service.list_all_streams(stream_filter, limit) streams = [s async for s in gen] - streams_api.v1_streams_list_post.assert_called_once_with(filter=stream_filter, skip=0, limit=limit, - session_token=SESSION_TOKEN) + streams_api.v1_streams_list_post.assert_called_once_with( + filter=stream_filter, skip=0, limit=limit, session_token=SESSION_TOKEN + ) assert len(streams) == 2 assert streams[0].id == "ouAS1QXEHtIhXdZq4NzJBX___oscpQFcdA" assert streams[0].room_attributes.name == "Room APP-3033" @@ -128,33 +141,44 @@ async def test_list_all_streams(mocked_api_client, stream_service, streams_api): async def test_add_member_to_room(stream_service, room_membership_api): user_id = 1234456 room_id = "room_id" - await stream_service.add_member_to_room(user_id, room_id) # check no exception raised + await stream_service.add_member_to_room( + user_id, room_id + ) # check no exception raised room_membership_api.v1_room_id_membership_add_post.assert_called_once_with( - payload=UserId(id=user_id), id=room_id, session_token=SESSION_TOKEN) + payload=UserId(id=user_id), id=room_id, session_token=SESSION_TOKEN + ) @pytest.mark.asyncio async def test_remove_member_from_room(stream_service, room_membership_api): user_id = 1234456 room_id = "room_id" - await stream_service.remove_member_from_room(user_id, room_id) # check no exception raised + await stream_service.remove_member_from_room( + user_id, room_id + ) # check no exception raised room_membership_api.v1_room_id_membership_remove_post.assert_called_once_with( - payload=UserId(id=user_id), id=room_id, session_token=SESSION_TOKEN) + payload=UserId(id=user_id), id=room_id, session_token=SESSION_TOKEN + ) @pytest.mark.asyncio async def test_share(mocked_api_client, stream_service, share_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V2Message, - "stream/share_article.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V2Message, "stream/share_article.json" + ) stream_id = "stream_id" share_content = ShareContent() message = await stream_service.share(stream_id, share_content) share_api.v3_stream_sid_share_post.assert_called_once_with( - sid=stream_id, share_content=share_content, session_token=SESSION_TOKEN, key_manager_token=KM_TOKEN) + sid=stream_id, + share_content=share_content, + session_token=SESSION_TOKEN, + key_manager_token=KM_TOKEN, + ) assert message.id == "uRcu9fjRALpKLHnPR1k6-3___oh4wwAObQ" assert message.stream_id == "ubaSiuUsc_j-_lVQ8vhAz3___opSJdJZdA" @@ -163,61 +187,77 @@ async def test_share(mocked_api_client, stream_service, share_api): async def test_promote_user(stream_service, room_membership_api): user_id = 12345 room_id = "room_id" - await stream_service.promote_user_to_room_owner(user_id, room_id) # check no exception raised + await stream_service.promote_user_to_room_owner( + user_id, room_id + ) # check no exception raised room_membership_api.v1_room_id_membership_promote_owner_post.assert_called_once_with( - id=room_id, payload=UserId(id=user_id), session_token=SESSION_TOKEN) + id=room_id, payload=UserId(id=user_id), session_token=SESSION_TOKEN + ) @pytest.mark.asyncio async def test_demote_owner(stream_service, room_membership_api): user_id = 12345 room_id = "room_id" - await stream_service.demote_owner_to_room_participant(user_id, room_id) # check no exception raised + await stream_service.demote_owner_to_room_participant( + user_id, room_id + ) # check no exception raised room_membership_api.v1_room_id_membership_demote_owner_post.assert_called_once_with( - id=room_id, payload=UserId(id=user_id), session_token=SESSION_TOKEN) + id=room_id, payload=UserId(id=user_id), session_token=SESSION_TOKEN + ) @pytest.mark.asyncio async def test_create_im(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(Stream, - "stream/create_im.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + Stream, "stream/create_im.json" + ) user_id = 12334 stream = await stream_service.create_im(user_id) - streams_api.v1_im_create_post.assert_called_once_with(uid_list=UserIdList(value=[user_id]), - session_token=SESSION_TOKEN) + streams_api.v1_im_create_post.assert_called_once_with( + uid_list=UserIdList(value=[user_id]), session_token=SESSION_TOKEN + ) assert stream.id == "-M8s5WG7K8lAP7cpIiuyTH___oh4zK8EdA" @pytest.mark.asyncio async def test_create_room(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V3RoomDetail, - "stream/create_room.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V3RoomDetail, "stream/create_room.json" + ) room_attributes = V3RoomAttributes() room_details = await stream_service.create_room(room_attributes) - streams_api.v3_room_create_post.assert_called_once_with(payload=room_attributes, session_token=SESSION_TOKEN) + streams_api.v3_room_create_post.assert_called_once_with( + payload=room_attributes, session_token=SESSION_TOKEN + ) assert room_details.room_attributes.name == "New fancy room" assert room_details.room_system_info.id == "7X1cP_3wMD4mr6rcp7j26X___oh4uDkVdA" @pytest.mark.asyncio async def test_search_rooms(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V3RoomSearchResults, - "stream/search_rooms.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V3RoomSearchResults, "stream/search_rooms.json" + ) search_criteria = V2RoomSearchCriteria(query="query") skip = 1 limit = 2 search_results = await stream_service.search_rooms(search_criteria, skip, limit) - streams_api.v3_room_search_post.assert_called_once_with(query=search_criteria, skip=skip, limit=limit, - include_non_discoverable=False, - session_token=SESSION_TOKEN) + streams_api.v3_room_search_post.assert_called_once_with( + query=search_criteria, + skip=skip, + limit=limit, + include_non_discoverable=False, + session_token=SESSION_TOKEN, + ) assert search_results.count == 1 assert len(search_results.rooms) == 1 assert search_results.rooms[0].room_attributes.name == "New room name" @@ -225,8 +265,9 @@ async def test_search_rooms(mocked_api_client, stream_service, streams_api): @pytest.mark.asyncio async def test_search_all_rooms(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V3RoomSearchResults, - "stream/search_rooms.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V3RoomSearchResults, "stream/search_rooms.json" + ) search_criteria = V2RoomSearchCriteria(query="query") chunk_size = 3 @@ -234,122 +275,150 @@ async def test_search_all_rooms(mocked_api_client, stream_service, streams_api): gen = await stream_service.search_all_rooms(search_criteria, chunk_size=chunk_size) search_results = [r async for r in gen] - streams_api.v3_room_search_post.assert_called_once_with(query=search_criteria, skip=0, limit=chunk_size, - include_non_discoverable=False, - session_token=SESSION_TOKEN) + streams_api.v3_room_search_post.assert_called_once_with( + query=search_criteria, + skip=0, + limit=chunk_size, + include_non_discoverable=False, + session_token=SESSION_TOKEN, + ) assert len(search_results) == 1 assert search_results[0].room_attributes.name == "New room name" @pytest.mark.asyncio async def test_get_room_info(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V3RoomDetail, - "stream/get_room_info.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V3RoomDetail, "stream/get_room_info.json" + ) room_id = "room_id" room_detail = await stream_service.get_room_info(room_id) - streams_api.v3_room_id_info_get.assert_called_once_with(id=room_id, session_token=SESSION_TOKEN) + streams_api.v3_room_id_info_get.assert_called_once_with( + id=room_id, session_token=SESSION_TOKEN + ) assert room_detail.room_attributes.name == "New room name" assert room_detail.room_system_info.id == "ubaSiuUsc_j-_lVQ8vhAz3___opSJdJZdA" @pytest.mark.asyncio async def test_get_im_info(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V1IMDetail, - "stream/get_im_info.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V1IMDetail, "stream/get_im_info.json" + ) im_id = "im_id" im_details = await stream_service.get_im_info(im_id) - streams_api.v1_im_id_info_get.assert_called_once_with(id=im_id, session_token=SESSION_TOKEN) - assert im_details.v1_im_attributes.pinned_message_id == "vd7qwNb6hLoUV0BfXXPC43___oPIvkwJbQ" + streams_api.v1_im_id_info_get.assert_called_once_with( + id=im_id, session_token=SESSION_TOKEN + ) + assert ( + im_details.v1_im_attributes.pinned_message_id + == "vd7qwNb6hLoUV0BfXXPC43___oPIvkwJbQ" + ) assert im_details.im_system_info.id == "usnBKBkH_BVrGOiVpaupEH___okFfE7QdA" @pytest.mark.asyncio async def test_set_room_active(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(RoomDetail, - "stream/deactivate_room.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + RoomDetail, "stream/deactivate_room.json" + ) room_id = "room_id" active = False room_detail = await stream_service.set_room_active(room_id, active) - streams_api.v1_room_id_set_active_post.assert_called_once_with(id=room_id, active=active, - session_token=SESSION_TOKEN) + streams_api.v1_room_id_set_active_post.assert_called_once_with( + id=room_id, active=active, session_token=SESSION_TOKEN + ) assert not room_detail.room_system_info.active @pytest.mark.asyncio async def test_update_room(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V3RoomDetail, - "stream/update_room.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V3RoomDetail, "stream/update_room.json" + ) room_id = "room_id" room_attributes = V3RoomAttributes() room_details = await stream_service.update_room(room_id, room_attributes) - streams_api.v3_room_id_update_post.assert_called_once_with(id=room_id, payload=room_attributes, - session_token=SESSION_TOKEN) + streams_api.v3_room_id_update_post.assert_called_once_with( + id=room_id, payload=room_attributes, session_token=SESSION_TOKEN + ) assert room_details.room_attributes.name == "Test bot room" assert room_details.room_system_info.id == "ubaSiuUsc_j-_lVQ8vhAz3___opSJdJZdA" @pytest.mark.asyncio async def test_update_im(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V1IMDetail, - "stream/get_im_info.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V1IMDetail, "stream/get_im_info.json" + ) im_id = "im_id" im_attributes = V1IMAttributes() im_details = await stream_service.update_im(im_id, im_attributes) - streams_api.v1_im_id_update_post.assert_called_once_with(id=im_id, payload=im_attributes, - session_token=SESSION_TOKEN) - assert im_details.v1_im_attributes.pinned_message_id == "vd7qwNb6hLoUV0BfXXPC43___oPIvkwJbQ" + streams_api.v1_im_id_update_post.assert_called_once_with( + id=im_id, payload=im_attributes, session_token=SESSION_TOKEN + ) + assert ( + im_details.v1_im_attributes.pinned_message_id + == "vd7qwNb6hLoUV0BfXXPC43___oPIvkwJbQ" + ) assert im_details.im_system_info.id == "usnBKBkH_BVrGOiVpaupEH___okFfE7QdA" @pytest.mark.asyncio async def test_create_im_admin(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(Stream, - "stream/create_im.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + Stream, "stream/create_im.json" + ) user_ids = [12334] stream = await stream_service.create_im_admin(user_ids) - streams_api.v1_admin_im_create_post.assert_called_once_with(uid_list=UserIdList(value=user_ids), - session_token=SESSION_TOKEN) + streams_api.v1_admin_im_create_post.assert_called_once_with( + uid_list=UserIdList(value=user_ids), session_token=SESSION_TOKEN + ) assert stream.id == "-M8s5WG7K8lAP7cpIiuyTH___oh4zK8EdA" @pytest.mark.asyncio async def test_set_room_active_admin(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(RoomDetail, - "stream/deactivate_room.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + RoomDetail, "stream/deactivate_room.json" + ) room_id = "room_id" active = False room_detail = await stream_service.set_room_active_admin(room_id, active) - streams_api.v1_admin_room_id_set_active_post.assert_called_once_with(id=room_id, active=active, - session_token=SESSION_TOKEN) + streams_api.v1_admin_room_id_set_active_post.assert_called_once_with( + id=room_id, active=active, session_token=SESSION_TOKEN + ) assert not room_detail.room_system_info.active @pytest.mark.asyncio async def test_list_streams_admin(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V2AdminStreamList, - "stream/list_streams_admin.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V2AdminStreamList, "stream/list_streams_admin.json" + ) stream_filter = V2AdminStreamFilter() skip = 1 limit = 2 streams = await stream_service.list_streams_admin(stream_filter, skip, limit) - streams_api.v2_admin_streams_list_post.assert_called_once_with(filter=stream_filter, skip=skip, limit=limit, - session_token=SESSION_TOKEN) + streams_api.v2_admin_streams_list_post.assert_called_once_with( + filter=stream_filter, skip=skip, limit=limit, session_token=SESSION_TOKEN + ) assert streams.limit == 2 assert len(streams.streams.value) == 2 assert streams.streams.value[0].id == "hpRd80zAUnLv3NMhLVF3Ln___o3ULKRDdA" @@ -358,8 +427,9 @@ async def test_list_streams_admin(mocked_api_client, stream_service, streams_api @pytest.mark.asyncio async def test_list_all_streams_admin(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V2AdminStreamList, - "stream/list_streams_admin.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V2AdminStreamList, "stream/list_streams_admin.json" + ) stream_filter = V2AdminStreamFilter() limit = 10 @@ -367,8 +437,9 @@ async def test_list_all_streams_admin(mocked_api_client, stream_service, streams gen = await stream_service.list_all_streams_admin(stream_filter, chunk_size=limit) streams = [s async for s in gen] - streams_api.v2_admin_streams_list_post.assert_called_once_with(filter=stream_filter, skip=0, limit=limit, - session_token=SESSION_TOKEN) + streams_api.v2_admin_streams_list_post.assert_called_once_with( + filter=stream_filter, skip=0, limit=limit, session_token=SESSION_TOKEN + ) assert len(streams) == 2 assert streams[0].id == "hpRd80zAUnLv3NMhLVF3Ln___o3ULKRDdA" assert streams[1].id == "6hEzTqQjVPLLE9KgXLvsKn___o3TtL3ddA" @@ -376,16 +447,18 @@ async def test_list_all_streams_admin(mocked_api_client, stream_service, streams @pytest.mark.asyncio async def test_list_stream_members(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V2MembershipList, - "stream/list_stream_members.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V2MembershipList, "stream/list_stream_members.json" + ) stream_id = "stream_id" skip = 1 limit = 2 members = await stream_service.list_stream_members(stream_id, skip, limit) - streams_api.v1_admin_stream_id_membership_list_get.assert_called_once_with(id=stream_id, skip=skip, limit=limit, - session_token=SESSION_TOKEN) + streams_api.v1_admin_stream_id_membership_list_get.assert_called_once_with( + id=stream_id, skip=skip, limit=limit, session_token=SESSION_TOKEN + ) assert members.count == 2 assert len(members.members.value) == 2 assert members.members.value[0].user.user_id == 13056700579872 @@ -394,31 +467,38 @@ async def test_list_stream_members(mocked_api_client, stream_service, streams_ap @pytest.mark.asyncio async def test_list_all_stream_members(mocked_api_client, stream_service, streams_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(V2MembershipList, - "stream/list_stream_members.json") + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + V2MembershipList, "stream/list_stream_members.json" + ) stream_id = "stream_id" limit = 5 gen = await stream_service.list_all_stream_members(stream_id, limit) members = [m async for m in gen] - streams_api.v1_admin_stream_id_membership_list_get.assert_called_once_with(id=stream_id, skip=0, limit=limit, - session_token=SESSION_TOKEN) + streams_api.v1_admin_stream_id_membership_list_get.assert_called_once_with( + id=stream_id, skip=0, limit=limit, session_token=SESSION_TOKEN + ) assert len(members) == 2 assert members[0].user.user_id == 13056700579872 assert members[1].user.user_id == 13056700579891 @pytest.mark.asyncio -async def test_list_room_members(mocked_api_client, stream_service, room_membership_api): - mocked_api_client.call_api.return_value = get_deserialized_object_from_resource(MembershipList, - "stream/list_room_members.json") +async def test_list_room_members( + mocked_api_client, stream_service, room_membership_api +): + mocked_api_client.call_api.return_value = get_deserialized_object_from_resource( + MembershipList, "stream/list_room_members.json" + ) room_id = "room_id" members = await stream_service.list_room_members(room_id) members = members.value - room_membership_api.v2_room_id_membership_list_get.assert_called_once_with(id=room_id, session_token=SESSION_TOKEN) + room_membership_api.v2_room_id_membership_list_get.assert_called_once_with( + id=room_id, session_token=SESSION_TOKEN + ) assert len(members) == 2 assert members[0].id == 13056700579872 assert members[1].id == 13056700579891 diff --git a/tests/core/service/stream/stream_util_test.py b/tests/core/service/stream/stream_util_test.py index ae9c312d..6b7b36e4 100644 --- a/tests/core/service/stream/stream_util_test.py +++ b/tests/core/service/stream/stream_util_test.py @@ -1,4 +1,7 @@ -from symphony.bdk.core.service.stream.stream_util import to_url_safe_stream_id, from_url_safe_stream_id +from symphony.bdk.core.service.stream.stream_util import ( + from_url_safe_stream_id, + to_url_safe_stream_id, +) def test_to_url_safe_stream_id(): diff --git a/tests/core/service/user/user_service_test.py b/tests/core/service/user/user_service_test.py index 130ba280..4d2ba47a 100644 --- a/tests/core/service/user/user_service_test.py +++ b/tests/core/service/user/user_service_test.py @@ -1,8 +1,7 @@ +import base64 import json -from pathlib import Path -from unittest.mock import MagicMock, AsyncMock, mock_open, patch +from unittest.mock import AsyncMock, MagicMock, mock_open, patch -import base64 import pytest from symphony.bdk.core.auth.auth_session import AuthSession @@ -16,7 +15,10 @@ from symphony.bdk.gen.pod_api.users_api import UsersApi from symphony.bdk.gen.pod_model.avatar_list import AvatarList from symphony.bdk.gen.pod_model.avatar_update import AvatarUpdate +from symphony.bdk.gen.pod_model.delegate_action import DelegateAction from symphony.bdk.gen.pod_model.disclaimer import Disclaimer +from symphony.bdk.gen.pod_model.feature import Feature +from symphony.bdk.gen.pod_model.feature_list import FeatureList from symphony.bdk.gen.pod_model.followers_list import FollowersList from symphony.bdk.gen.pod_model.followers_list_response import FollowersListResponse from symphony.bdk.gen.pod_model.following_list_response import FollowingListResponse @@ -30,19 +32,19 @@ from symphony.bdk.gen.pod_model.user_search_filter import UserSearchFilter from symphony.bdk.gen.pod_model.user_search_query import UserSearchQuery from symphony.bdk.gen.pod_model.user_search_results import UserSearchResults -from symphony.bdk.gen.pod_model.v2_user_detail import V2UserDetail -from symphony.bdk.gen.pod_model.v2_user_detail_list import V2UserDetailList -from symphony.bdk.gen.pod_model.delegate_action import DelegateAction -from symphony.bdk.gen.pod_model.feature_list import FeatureList -from symphony.bdk.gen.pod_model.feature import Feature from symphony.bdk.gen.pod_model.user_status import UserStatus -from symphony.bdk.gen.pod_model.v2_user_create import V2UserCreate -from symphony.bdk.gen.pod_model.v2_user_attributes import V2UserAttributes from symphony.bdk.gen.pod_model.user_suspension import UserSuspension +from symphony.bdk.gen.pod_model.v2_user_attributes import V2UserAttributes +from symphony.bdk.gen.pod_model.v2_user_create import V2UserCreate +from symphony.bdk.gen.pod_model.v2_user_detail import V2UserDetail +from symphony.bdk.gen.pod_model.v2_user_detail_list import V2UserDetailList from symphony.bdk.gen.pod_model.v2_user_list import V2UserList from tests.core.config import minimal_retry_config -from tests.utils.resource_utils import get_resource_filepath, get_deserialized_object_from_resource, \ - deserialize_object +from tests.utils.resource_utils import ( + deserialize_object, + get_deserialized_object_from_resource, + get_resource_filepath, +) @pytest.fixture(name="auth_session") @@ -82,21 +84,21 @@ def fixture_user_service(user_api, users_api, audit_trail_api, system_api, auth_ system_api, auth_session, minimal_retry_config(), - "manifest.json" + "manifest.json", ) return service + @pytest.mark.asyncio async def test_list_users_by_ids(users_api, user_service): users_api.v3_users_get = AsyncMock() - users_api.v3_users_get.return_value = get_deserialized_object_from_resource(V2UserList, "user/list_user.json") + users_api.v3_users_get.return_value = get_deserialized_object_from_resource( + V2UserList, "user/list_user.json" + ) users_list = await user_service.list_users_by_ids([15942919536460, 15942919536461], active=True) users_api.v3_users_get.assert_called_with( - uid="15942919536460,15942919536461", - local=False, - session_token="session_token", - active=True + uid="15942919536460,15942919536461", local=False, session_token="session_token", active=True ) assert len(users_list.users) == 2 assert len(users_list.errors) == 2 @@ -106,17 +108,18 @@ async def test_list_users_by_ids(users_api, user_service): @pytest.mark.asyncio async def test_list_users_by_emails(users_api, user_service): users_api.v3_users_get = AsyncMock() - users_api.v3_users_get.return_value = get_deserialized_object_from_resource(V2UserList, "user/list_user.json") + users_api.v3_users_get.return_value = get_deserialized_object_from_resource( + V2UserList, "user/list_user.json" + ) users_list = await user_service.list_users_by_emails( - ["technicalwriter@symphony.com", "serviceaccount@symphony.com"], - active=True + ["technicalwriter@symphony.com", "serviceaccount@symphony.com"], active=True ) users_api.v3_users_get.assert_called_with( email="technicalwriter@symphony.com,serviceaccount@symphony.com", local=False, session_token="session_token", - active=True + active=True, ) assert len(users_list.users) == 2 assert len(users_list.errors) == 2 @@ -126,17 +129,13 @@ async def test_list_users_by_emails(users_api, user_service): @pytest.mark.asyncio async def test_list_users_by_usernames(users_api, user_service): users_api.v3_users_get = AsyncMock() - users_api.v3_users_get.return_value = get_deserialized_object_from_resource(V2UserList, "user/list_user.json") - users_list = await user_service.list_users_by_usernames( - ["tw", "SA"], - active=True + users_api.v3_users_get.return_value = get_deserialized_object_from_resource( + V2UserList, "user/list_user.json" ) + users_list = await user_service.list_users_by_usernames(["tw", "SA"], active=True) users_api.v3_users_get.assert_called_with( - username="tw,SA", - local=True, - session_token="session_token", - active=True + username="tw,SA", local=True, session_token="session_token", active=True ) assert len(users_list.users) == 2 @@ -147,18 +146,17 @@ async def test_list_users_by_usernames(users_api, user_service): @pytest.mark.asyncio async def test_search_users(users_api, user_service): users_api.v1_user_search_post = AsyncMock() - users_api.v1_user_search_post.return_value = get_deserialized_object_from_resource(UserSearchResults, - "user/search_user.json") - query = UserSearchQuery(query="jane", filters=UserSearchFilter(title="Sales Manager", company="Symphony")) + users_api.v1_user_search_post.return_value = get_deserialized_object_from_resource( + UserSearchResults, "user/search_user.json" + ) + query = UserSearchQuery( + query="jane", filters=UserSearchFilter(title="Sales Manager", company="Symphony") + ) result = await user_service.search_users(query) users_api.v1_user_search_post.assert_called_with( - search_request=query, - local=False, - skip=0, - limit=50, - session_token="session_token" + search_request=query, local=False, skip=0, limit=50, session_token="session_token" ) assert result.count == 1 assert len(result.users) == 1 @@ -168,19 +166,18 @@ async def test_search_users(users_api, user_service): @pytest.mark.asyncio async def test_search_all_users(users_api, user_service): users_api.v1_user_search_post = AsyncMock() - users_api.v1_user_search_post.return_value = get_deserialized_object_from_resource(UserSearchResults, - "user/search_user.json") - query = UserSearchQuery(query="jane", filters=UserSearchFilter(title="Sales Manager", company="Symphony")) + users_api.v1_user_search_post.return_value = get_deserialized_object_from_resource( + UserSearchResults, "user/search_user.json" + ) + query = UserSearchQuery( + query="jane", filters=UserSearchFilter(title="Sales Manager", company="Symphony") + ) gen = await user_service.search_all_users(query) result = [u async for u in gen] users_api.v1_user_search_post.assert_called_with( - search_request=query, - local=False, - skip=0, - limit=50, - session_token="session_token" + search_request=query, local=False, skip=0, limit=50, session_token="session_token" ) assert len(result) == 1 assert result[0].id == 13056700581099 @@ -195,7 +192,7 @@ async def test_follow_user(user_api, user_service): user_api.v1_user_uid_follow_post.assert_called_with( uid=12345, uid_list=FollowersList(followers=UserIdList(value=[1234, 2345])), - session_token="session_token" + session_token="session_token", ) @@ -208,21 +205,21 @@ async def test_unfollow_user(user_api, user_service): user_api.v1_user_uid_unfollow_post.assert_called_with( uid=12345, uid_list=FollowersList(followers=UserIdList(value=[1234, 2345])), - session_token="session_token" + session_token="session_token", ) @pytest.mark.asyncio async def test_get_user_detail(user_api, user_service): user_api.v2_admin_user_uid_get = AsyncMock() - user_api.v2_admin_user_uid_get.return_value = get_deserialized_object_from_resource(V2UserDetail, - "user/user_detail.json") + user_api.v2_admin_user_uid_get.return_value = get_deserialized_object_from_resource( + V2UserDetail, "user/user_detail.json" + ) user_detail = await user_service.get_user_detail(7215545078461) user_api.v2_admin_user_uid_get.assert_called_with( - uid=7215545078461, - session_token="session_token" + uid=7215545078461, session_token="session_token" ) assert user_detail.user_attributes.user_name == "johndoe" @@ -233,15 +230,14 @@ async def test_get_user_detail(user_api, user_service): @pytest.mark.asyncio async def test_list_user_details(user_api, user_service): user_api.v2_admin_user_list_get = AsyncMock() - user_api.v2_admin_user_list_get.return_value = get_deserialized_object_from_resource(V2UserDetailList, - "user/list_user_detail.json") + user_api.v2_admin_user_list_get.return_value = get_deserialized_object_from_resource( + V2UserDetailList, "user/list_user_detail.json" + ) user_detail_list = await user_service.list_user_details() user_api.v2_admin_user_list_get.assert_called_with( - skip=0, - limit=50, - session_token="session_token" + skip=0, limit=50, session_token="session_token" ) assert len(user_detail_list) == 5 @@ -252,16 +248,15 @@ async def test_list_user_details(user_api, user_service): @pytest.mark.asyncio async def test_list_all_user_details(user_api, user_service): user_api.v2_admin_user_list_get = AsyncMock() - user_api.v2_admin_user_list_get.return_value = get_deserialized_object_from_resource(V2UserDetailList, - "user/list_user_detail.json") + user_api.v2_admin_user_list_get.return_value = get_deserialized_object_from_resource( + V2UserDetailList, "user/list_user_detail.json" + ) gen = await user_service.list_all_user_details() user_detail_list = [u async for u in gen] user_api.v2_admin_user_list_get.assert_called_with( - skip=0, - limit=50, - session_token="session_token" + skip=0, limit=50, session_token="session_token" ) assert len(user_detail_list) == 5 @@ -272,17 +267,15 @@ async def test_list_all_user_details(user_api, user_service): @pytest.mark.asyncio async def test_list_user_details_by_filter(user_api, user_service): user_api.v1_admin_user_find_post = AsyncMock() - user_api.v1_admin_user_find_post.return_value = \ - get_deserialized_object_from_resource(UserDetailList, "user/list_user_by_filter.json") + user_api.v1_admin_user_find_post.return_value = get_deserialized_object_from_resource( + UserDetailList, "user/list_user_by_filter.json" + ) user_filter = UserFilter(status="ENABLED", role="INDIVIDUAL") user_detail_list = await user_service.list_user_details_by_filter(user_filter) user_api.v1_admin_user_find_post.assert_called_with( - skip=0, - limit=50, - payload=user_filter, - session_token="session_token" + skip=0, limit=50, payload=user_filter, session_token="session_token" ) assert len(user_detail_list) == 3 @@ -293,18 +286,16 @@ async def test_list_user_details_by_filter(user_api, user_service): @pytest.mark.asyncio async def test_list_all_user_details_by_filter(user_api, user_service): user_api.v1_admin_user_find_post = AsyncMock() - user_api.v1_admin_user_find_post.return_value = \ - get_deserialized_object_from_resource(UserDetailList, "user/list_user_by_filter.json") + user_api.v1_admin_user_find_post.return_value = get_deserialized_object_from_resource( + UserDetailList, "user/list_user_by_filter.json" + ) user_filter = UserFilter(status="ENABLED") gen = await user_service.list_all_user_details_by_filter(user_filter) user_detail_list = [u async for u in gen] user_api.v1_admin_user_find_post.assert_called_with( - skip=0, - limit=50, - payload=user_filter, - session_token="session_token" + skip=0, limit=50, payload=user_filter, session_token="session_token" ) assert len(user_detail_list) == 3 @@ -319,23 +310,20 @@ async def test_add_role(user_api, user_service): await user_service.add_role(1234, RoleId.INDIVIDUAL) user_api.v1_admin_user_uid_roles_add_post.assert_called_with( - uid=1234, - payload=StringId(id="INDIVIDUAL"), - session_token="session_token" + uid=1234, payload=StringId(id="INDIVIDUAL"), session_token="session_token" ) @pytest.mark.asyncio async def test_list_roles(system_api, user_service): system_api.v1_admin_system_roles_list_get = AsyncMock() - system_api.v1_admin_system_roles_list_get.return_value = \ - get_deserialized_object_from_resource(RoleDetailList, "user/list_roles.json") + system_api.v1_admin_system_roles_list_get.return_value = get_deserialized_object_from_resource( + RoleDetailList, "user/list_roles.json" + ) role_list = await user_service.list_roles() - system_api.v1_admin_system_roles_list_get.assert_called_with( - session_token="session_token" - ) + system_api.v1_admin_system_roles_list_get.assert_called_with(session_token="session_token") assert len(role_list) == 12 assert role_list[0].id == "CONTENT_MANAGEMENT" @@ -349,28 +337,29 @@ async def test_remove_role(user_api, user_service): await user_service.remove_role(1234, RoleId.INDIVIDUAL) user_api.v1_admin_user_uid_roles_remove_post.assert_called_with( - uid=1234, - payload=StringId(id="INDIVIDUAL"), - session_token="session_token" + uid=1234, payload=StringId(id="INDIVIDUAL"), session_token="session_token" ) @pytest.mark.asyncio async def test_get_avatar(user_api, user_service): user_api.v1_admin_user_uid_avatar_get = AsyncMock() - user_api.v1_admin_user_uid_avatar_get.return_value = \ - get_deserialized_object_from_resource(AvatarList, "user/list_avatar.json") + user_api.v1_admin_user_uid_avatar_get.return_value = get_deserialized_object_from_resource( + AvatarList, "user/list_avatar.json" + ) avatar_list = await user_service.get_avatar(1234) user_api.v1_admin_user_uid_avatar_get.assert_called_with( - uid=1234, - session_token="session_token" + uid=1234, session_token="session_token" ) assert len(avatar_list) == 5 assert avatar_list[0].size == "600" - assert avatar_list[1].url == "../avatars/acme/150/7215545057281/3gXMhglCCTwLPL9JAprnyHzYn5-PR49-wYDG814n1g8.png" + assert ( + avatar_list[1].url + == "../avatars/acme/150/7215545057281/3gXMhglCCTwLPL9JAprnyHzYn5-PR49-wYDG814n1g8.png" + ) @pytest.mark.asyncio @@ -379,9 +368,7 @@ async def test_update_avatar(user_api, user_service): await user_service.update_avatar(1234, "image_string") user_api.v1_admin_user_uid_avatar_update_post.assert_called_with( - uid=1234, - payload=AvatarUpdate(image="image_string"), - session_token="session_token" + uid=1234, payload=AvatarUpdate(image="image_string"), session_token="session_token" ) with open(get_resource_filepath("user/FINOS_icon_rgb.png", as_text=True), "rb") as file: @@ -391,21 +378,21 @@ async def test_update_avatar(user_api, user_service): user_api.v1_admin_user_uid_avatar_update_post.assert_called_with( uid=1234, payload=AvatarUpdate(image=str(base64.standard_b64encode(avatar_bytes))), - session_token="session_token" + session_token="session_token", ) @pytest.mark.asyncio async def test_get_disclaimer(user_api, user_service): user_api.v1_admin_user_uid_disclaimer_get = AsyncMock() - user_api.v1_admin_user_uid_disclaimer_get.return_value = \ - get_deserialized_object_from_resource(Disclaimer, "disclaimer/disclaimer.json") + user_api.v1_admin_user_uid_disclaimer_get.return_value = get_deserialized_object_from_resource( + Disclaimer, "disclaimer/disclaimer.json" + ) disclaimer = await user_service.get_disclaimer(1234) user_api.v1_admin_user_uid_disclaimer_get.assert_called_with( - uid=1234, - session_token="session_token" + uid=1234, session_token="session_token" ) assert disclaimer.id == "571d2052e4b042aaf06d2e7a" @@ -419,8 +406,7 @@ async def test_remove_disclaimer(user_api, user_service): await user_service.remove_disclaimer(1234) user_api.v1_admin_user_uid_disclaimer_delete.assert_called_with( - uid=1234, - session_token="session_token" + uid=1234, session_token="session_token" ) @@ -431,22 +417,21 @@ async def test_add_disclaimer(user_api, user_service): await user_service.add_disclaimer(1234, "disclaimer_id") user_api.v1_admin_user_uid_disclaimer_update_post.assert_called_with( - uid=1234, - payload=StringId(id="disclaimer_id"), - session_token="session_token" + uid=1234, payload=StringId(id="disclaimer_id"), session_token="session_token" ) @pytest.mark.asyncio async def test_get_delegates(user_api, user_service): user_api.v1_admin_user_uid_delegates_get = AsyncMock() - user_api.v1_admin_user_uid_delegates_get.return_value = deserialize_object(IntegerList, "[7215545078461]") + user_api.v1_admin_user_uid_delegates_get.return_value = deserialize_object( + IntegerList, "[7215545078461]" + ) delegate_list = await user_service.get_delegates(1234) user_api.v1_admin_user_uid_delegates_get.assert_called_with( - uid=1234, - session_token="session_token" + uid=1234, session_token="session_token" ) assert len(delegate_list) == 1 @@ -462,29 +447,30 @@ async def test_update_delegates(user_api, user_service): user_api.v1_admin_user_uid_delegates_update_post.assert_called_with( uid=7215545078541, payload=DelegateAction(user_id=7215545078461, action=DelegateActionEnum.ADD.value), - session_token="session_token" + session_token="session_token", ) @pytest.mark.asyncio async def test_get_feature_entitlements(user_api, user_service): user_api.v1_admin_user_uid_features_get = AsyncMock() - user_api.v1_admin_user_uid_features_get.return_value = \ - deserialize_object(FeatureList, payload="[" - " {" - " \"entitlment\": \"canCreatePublicRoom\"," - " \"enabled\": true}," - " { " - " \"entitlment\": \"isExternalRoomEnabled\"," - " \"enabled\": false" - " }" - "]") + user_api.v1_admin_user_uid_features_get.return_value = deserialize_object( + FeatureList, + payload="[" + " {" + ' "entitlment": "canCreatePublicRoom",' + ' "enabled": true},' + " { " + ' "entitlment": "isExternalRoomEnabled",' + ' "enabled": false' + " }" + "]", + ) feature_list = await user_service.get_feature_entitlements(1234) user_api.v1_admin_user_uid_features_get.assert_called_with( - uid=1234, - session_token="session_token" + uid=1234, session_token="session_token" ) assert len(feature_list) == 2 @@ -500,22 +486,21 @@ async def test_update_feature_entitlements(user_api, user_service): await user_service.update_feature_entitlements(1234, [feature]) user_api.v1_admin_user_uid_features_update_post.assert_called_with( - uid=1234, - payload=FeatureList(value=[feature]), - session_token="session_token" + uid=1234, payload=FeatureList(value=[feature]), session_token="session_token" ) @pytest.mark.asyncio async def test_get_status(user_api, user_service): user_api.v1_admin_user_uid_status_get = AsyncMock() - user_api.v1_admin_user_uid_status_get.return_value = deserialize_object(UserStatus, "{\"status\": \"ENABLED\"}") + user_api.v1_admin_user_uid_status_get.return_value = deserialize_object( + UserStatus, '{"status": "ENABLED"}' + ) status = await user_service.get_status(1234) user_api.v1_admin_user_uid_status_get.assert_called_with( - uid=1234, - session_token="session_token" + uid=1234, session_token="session_token" ) assert status.status == "ENABLED" @@ -529,26 +514,21 @@ async def test_update_status(user_api, user_service): await user_service.update_status(1234, status) user_api.v1_admin_user_uid_status_update_post.assert_called_with( - uid=1234, - payload=status, - session_token="session_token" + uid=1234, payload=status, session_token="session_token" ) @pytest.mark.asyncio async def test_list_user_followers(user_api, user_service): user_api.v1_user_uid_followers_get = AsyncMock() - user_api.v1_user_uid_followers_get.return_value = \ - get_deserialized_object_from_resource(FollowersListResponse, "user/list_user_followers.json") + user_api.v1_user_uid_followers_get.return_value = get_deserialized_object_from_resource( + FollowersListResponse, "user/list_user_followers.json" + ) follower_list = await user_service.list_user_followers(1234, before=4, after=1) user_api.v1_user_uid_followers_get.assert_called_with( - uid=1234, - limit=100, - session_token="session_token", - before=4, - after=1 + uid=1234, limit=100, session_token="session_token", before=4, after=1 ) assert follower_list.count == 5 @@ -559,8 +539,9 @@ async def test_list_user_followers(user_api, user_service): @pytest.mark.asyncio async def test_list_all_user_followers(user_api, user_service): user_api.v1_user_uid_followers_get = AsyncMock() - user_api.v1_user_uid_followers_get.return_value = \ - get_deserialized_object_from_resource(FollowersListResponse, "user/list_user_followers.json") + user_api.v1_user_uid_followers_get.return_value = get_deserialized_object_from_resource( + FollowersListResponse, "user/list_user_followers.json" + ) gen = await user_service.list_all_user_followers(1234, max_number=2) follower_list = [uid async for uid in gen] @@ -579,17 +560,14 @@ async def test_list_all_user_followers(user_api, user_service): @pytest.mark.asyncio async def test_list_users_following(user_api, user_service): user_api.v1_user_uid_following_get = AsyncMock() - user_api.v1_user_uid_following_get.return_value = \ - get_deserialized_object_from_resource(FollowingListResponse, "user/list_users_following.json") + user_api.v1_user_uid_following_get.return_value = get_deserialized_object_from_resource( + FollowingListResponse, "user/list_users_following.json" + ) following_user_list = await user_service.list_users_following(1234, before=4, after=1) user_api.v1_user_uid_following_get.assert_called_with( - uid=1234, - limit=100, - session_token="session_token", - before=4, - after=1 + uid=1234, limit=100, session_token="session_token", before=4, after=1 ) assert following_user_list.count == 3 @@ -600,16 +578,15 @@ async def test_list_users_following(user_api, user_service): @pytest.mark.asyncio async def test_list_all_users_following(user_api, user_service): user_api.v1_user_uid_following_get = AsyncMock() - user_api.v1_user_uid_following_get.return_value = \ - get_deserialized_object_from_resource(FollowingListResponse, "user/list_users_following.json") + user_api.v1_user_uid_following_get.return_value = get_deserialized_object_from_resource( + FollowingListResponse, "user/list_users_following.json" + ) gen = await user_service.list_all_users_following(1234, max_number=2) following_user_list = [uid async for uid in gen] user_api.v1_user_uid_following_get.assert_called_with( - uid=1234, - limit=100, - session_token="session_token" + uid=1234, limit=100, session_token="session_token" ) assert len(following_user_list) == 2 @@ -620,15 +597,15 @@ async def test_list_all_users_following(user_api, user_service): @pytest.mark.asyncio async def test_create(user_api, user_service): user_api.v2_admin_user_create_post = AsyncMock() - user_api.v2_admin_user_create_post.return_value = get_deserialized_object_from_resource(V2UserDetail, - "user/user_detail.json") + user_api.v2_admin_user_create_post.return_value = get_deserialized_object_from_resource( + V2UserDetail, "user/user_detail.json" + ) user_create = V2UserCreate() user_detail = await user_service.create(user_create) user_api.v2_admin_user_create_post.assert_called_with( - payload=user_create, - session_token="session_token" + payload=user_create, session_token="session_token" ) assert user_detail.user_attributes.user_name == "johndoe" @@ -639,17 +616,16 @@ async def test_create(user_api, user_service): @pytest.mark.asyncio async def test_update(user_api, user_service): user_api.v2_admin_user_uid_update_post = AsyncMock() - user_api.v2_admin_user_uid_update_post.return_value = get_deserialized_object_from_resource(V2UserDetail, - "user/user_detail.json") + user_api.v2_admin_user_uid_update_post.return_value = get_deserialized_object_from_resource( + V2UserDetail, "user/user_detail.json" + ) user_attribute = V2UserAttributes() user_detail = await user_service.update(1234, user_attribute) user_api.v2_admin_user_uid_update_post.assert_called_with( - uid=1234, - payload=user_attribute, - session_token="session_token" + uid=1234, payload=user_attribute, session_token="session_token" ) assert user_detail.user_attributes.user_name == "johndoe" @@ -660,11 +636,15 @@ async def test_update(user_api, user_service): @pytest.mark.asyncio async def test_list_audit_trail(audit_trail_api, user_service): audit_trail_api.v1_audittrail_privilegeduser_get = AsyncMock() - audit_trail_api.v1_audittrail_privilegeduser_get.return_value = \ - get_deserialized_object_from_resource(V1AuditTrailInitiatorList, "user/list_audit_trail.json") + audit_trail_api.v1_audittrail_privilegeduser_get.return_value = ( + get_deserialized_object_from_resource( + V1AuditTrailInitiatorList, "user/list_audit_trail.json" + ) + ) - audit_trail_initiator_list = \ - await user_service.list_audit_trail(1234, 2345, 12345, RoleId.SUPER_ADMINISTRATOR, before=1, after=4) + audit_trail_initiator_list = await user_service.list_audit_trail( + 1234, 2345, 12345, RoleId.SUPER_ADMINISTRATOR, before=1, after=4 + ) audit_trail_api.v1_audittrail_privilegeduser_get.assert_called_with( start_timestamp=1234, @@ -675,24 +655,30 @@ async def test_list_audit_trail(audit_trail_api, user_service): after=4, limit=50, session_token="session_token", - key_manager_token="km_token" + key_manager_token="km_token", ) assert len(audit_trail_initiator_list.items) == 2 assert audit_trail_initiator_list.items[0].initiator_id == 1353716993 + @pytest.mark.asyncio async def test_list_all_user_audit_trail(audit_trail_api, user_service): audit_trail_api.v1_audittrail_privilegeduser_get = AsyncMock() - audit_trail_api.v1_audittrail_privilegeduser_get.return_value = \ - get_deserialized_object_from_resource(V1AuditTrailInitiatorList, "user/list_all_audit_trail_one_page.json") - - gen = await user_service.list_all_audit_trail(start_timestamp=2345, - end_timestamp=12345, - initiator_id=1234, - role=RoleId.SUPER_ADMINISTRATOR, - chunk_size=2, - max_number=2) + audit_trail_api.v1_audittrail_privilegeduser_get.return_value = ( + get_deserialized_object_from_resource( + V1AuditTrailInitiatorList, "user/list_all_audit_trail_one_page.json" + ) + ) + + gen = await user_service.list_all_audit_trail( + start_timestamp=2345, + end_timestamp=12345, + initiator_id=1234, + role=RoleId.SUPER_ADMINISTRATOR, + chunk_size=2, + max_number=2, + ) audit_trail_list = [audit async for audit in gen] audit_trail_api.v1_audittrail_privilegeduser_get.assert_called_with( @@ -702,66 +688,75 @@ async def test_list_all_user_audit_trail(audit_trail_api, user_service): role="SUPER_ADMINISTRATOR", limit=2, session_token="session_token", - key_manager_token="km_token") + key_manager_token="km_token", + ) assert len(audit_trail_list) == 2 assert audit_trail_list[0]["action_name"] == "profileInfoUpdate" assert audit_trail_list[1]["action_name"] == "userAppEntitlementInstall" + @pytest.mark.asyncio async def test_list_all_user_audit_trail_2_pages(audit_trail_api, user_service): audit_trail_api.v1_audittrail_privilegeduser_get = AsyncMock() - return_values = [get_deserialized_object_from_resource( - V1AuditTrailInitiatorList, "user/list_all_audit_trail_page_1.json"), - get_deserialized_object_from_resource( - V1AuditTrailInitiatorList, "user/list_all_audit_trail_page_2.json")] + return_values = [ + get_deserialized_object_from_resource( + V1AuditTrailInitiatorList, "user/list_all_audit_trail_page_1.json" + ), + get_deserialized_object_from_resource( + V1AuditTrailInitiatorList, "user/list_all_audit_trail_page_2.json" + ), + ] audit_trail_api.v1_audittrail_privilegeduser_get.side_effect = return_values - gen = await user_service.list_all_audit_trail(start_timestamp=2345, - end_timestamp=12345, - initiator_id=1234, - role=RoleId.SUPER_ADMINISTRATOR, - chunk_size=2, - max_number=4) + gen = await user_service.list_all_audit_trail( + start_timestamp=2345, + end_timestamp=12345, + initiator_id=1234, + role=RoleId.SUPER_ADMINISTRATOR, + chunk_size=2, + max_number=4, + ) audit_trail_list = [audit async for audit in gen] args, kwargs = audit_trail_api.v1_audittrail_privilegeduser_get.call_args assert audit_trail_api.v1_audittrail_privilegeduser_get.call_count == 2 - assert kwargs['after'] == '2' + assert kwargs["after"] == "2" assert len(audit_trail_list) == 4 assert audit_trail_list[0]["action_name"] == "profileInfoUpdate-2" assert audit_trail_list[1]["action_name"] == "userAppEntitlementInstall-2" assert audit_trail_list[2]["action_name"] == "profileInfoUpdate" assert audit_trail_list[3]["action_name"] == "userAppEntitlementInstall" + @pytest.mark.asyncio async def test_suspend_user(user_api, user_service): user_api.v1_admin_user_user_id_suspension_update_put = AsyncMock() - user_suspension = UserSuspension(suspended=True, suspended_until=1601596799999, suspension_reason="testing") + user_suspension = UserSuspension( + suspended=True, suspended_until=1601596799999, suspension_reason="testing" + ) await user_service.suspend_user(1234, user_suspension) user_api.v1_admin_user_user_id_suspension_update_put.assert_called_with( - user_id=1234, - payload=user_suspension, - session_token="session_token" + user_id=1234, payload=user_suspension, session_token="session_token" ) @pytest.mark.asyncio async def test_suspend(user_api, user_service): user_api.v1_admin_user_user_id_suspension_update_put = AsyncMock() - user_suspension = UserSuspension(suspended=True, suspended_until=1601596799999, suspension_reason="testing") + user_suspension = UserSuspension( + suspended=True, suspended_until=1601596799999, suspension_reason="testing" + ) await user_service.suspend(user_id=1234, reason="testing", until=1601596799999) user_api.v1_admin_user_user_id_suspension_update_put.assert_called_with( - user_id=1234, - payload=user_suspension, - session_token="session_token" + user_id=1234, payload=user_suspension, session_token="session_token" ) @@ -773,11 +768,10 @@ async def test_unsuspend(user_api, user_service): await user_service.unsuspend(1234) user_api.v1_admin_user_user_id_suspension_update_put.assert_called_with( - user_id=1234, - payload=user_suspension, - session_token="session_token" + user_id=1234, payload=user_suspension, session_token="session_token" ) + @pytest.mark.asyncio async def test_get_manifest(user_api, user_service): user_api.v1_user_manifest_own_get = AsyncMock() @@ -789,24 +783,27 @@ async def test_get_manifest(user_api, user_service): @pytest.mark.asyncio async def test_update_manifest_file(user_api, user_service): - expected_manifest_data = {"commands": [ - { - "args": [{"name": "[query1]"},{"name": "[query2]"}], - "desc": "Search users based on name, company or job title", - "example": "/search John Smith", - "name": "search" - } - ]} + expected_manifest_data = { + "commands": [ + { + "args": [{"name": "[query1]"}, {"name": "[query2]"}], + "desc": "Search users based on name, company or job title", + "example": "/search John Smith", + "name": "search", + } + ] + } mock_file_content_json = json.dumps(expected_manifest_data) - with patch('pathlib.Path.open', new_callable=mock_open, read_data=mock_file_content_json) as mock_path_open: + with patch( + "pathlib.Path.open", new_callable=mock_open, read_data=mock_file_content_json + ) as mock_path_open: user_api.v1_user_manifest_own_post = AsyncMock() - await user_service.update_manifest_from_file() mock_path_open.assert_called_once() user_api.v1_user_manifest_own_post.assert_called_once_with( session_token="session_token", - manifest=ServiceAccountManifest(json.dumps(expected_manifest_data)) - ) \ No newline at end of file + manifest=ServiceAccountManifest(json.dumps(expected_manifest_data)), + ) diff --git a/tests/core/service/user/user_util_test.py b/tests/core/service/user/user_util_test.py index d776d75f..e441112e 100644 --- a/tests/core/service/user/user_util_test.py +++ b/tests/core/service/user/user_util_test.py @@ -1,6 +1,6 @@ import pytest -from symphony.bdk.core.service.user.user_util import extract_tenant_id, NumberUtil +from symphony.bdk.core.service.user.user_util import NumberUtil, extract_tenant_id def test_extract_tenant_id(): diff --git a/tests/core/symphony_bdk_test.py b/tests/core/symphony_bdk_test.py index c4c69285..696b374c 100644 --- a/tests/core/symphony_bdk_test.py +++ b/tests/core/symphony_bdk_test.py @@ -1,5 +1,4 @@ -from unittest.mock import AsyncMock, Mock -from unittest.mock import patch +from unittest.mock import AsyncMock, Mock, patch import pytest @@ -8,7 +7,7 @@ from symphony.bdk.core.auth.exception import AuthInitializationError from symphony.bdk.core.auth.obo_authenticator import OboAuthenticatorRsa from symphony.bdk.core.client.api_client_factory import ApiClientFactory -from symphony.bdk.core.config.exception import BotNotConfiguredError, BdkConfigError +from symphony.bdk.core.config.exception import BdkConfigError, BotNotConfiguredError from symphony.bdk.core.config.loader import BdkConfigLoader from symphony.bdk.core.config.model.bdk_config import BdkConfig from symphony.bdk.core.extension import ExtensionService @@ -33,7 +32,9 @@ def fixture_invalid_app_id_config(): @pytest.fixture(name="obo_only_config") def fixture_obo_only_config(): - return BdkConfig(host="acme.symphony.com", app={"appId": "app", "privateKey": {"path": "/path/to/key.pem"}}) + return BdkConfig( + host="acme.symphony.com", app={"appId": "app", "privateKey": {"path": "/path/to/key.pem"}} + ) @pytest.fixture(name="mock_obo_session") @@ -46,7 +47,9 @@ def fixture_mock_obo_session(): @pytest.mark.asyncio async def test_bot_session(config): - with patch("symphony.bdk.core.auth.bot_authenticator.create_signed_jwt", return_value="privateKey"): + with patch( + "symphony.bdk.core.auth.bot_authenticator.create_signed_jwt", return_value="privateKey" + ): bot_authenticator = AsyncMock(BotAuthenticatorRsa) bot_authenticator.retrieve_session_token.return_value = "session_token" bot_authenticator.retrieve_key_manager_token.return_value = "km_token" @@ -125,8 +128,10 @@ async def test_obo_with_username(config): @pytest.mark.asyncio async def test_obo_with_user_id_and_username(config): - with patch.object(OboAuthenticatorRsa, "authenticate_by_username") as authenticate_by_username, \ - patch.object(OboAuthenticatorRsa, "authenticate_by_user_id") as authenticate_by_user_id: + with ( + patch.object(OboAuthenticatorRsa, "authenticate_by_username") as authenticate_by_username, + patch.object(OboAuthenticatorRsa, "authenticate_by_user_id") as authenticate_by_user_id, + ): authenticate_by_user_id.return_value = Mock(OboAuthSession) user_id = 12345 @@ -186,7 +191,9 @@ async def test_ext_app_authenticator(obo_only_config): async with SymphonyBdk(obo_only_config) as symphony_bdk: authenticator = symphony_bdk.app_authenticator() assert authenticator is not None - assert symphony_bdk.app_authenticator() == authenticator # test same instance is always returned + assert ( + symphony_bdk.app_authenticator() == authenticator + ) # test same instance is always returned @pytest.mark.asyncio diff --git a/tests/core/test/in_memory_datafeed_id_repository.py b/tests/core/test/in_memory_datafeed_id_repository.py index 528c0cf4..9810caea 100644 --- a/tests/core/test/in_memory_datafeed_id_repository.py +++ b/tests/core/test/in_memory_datafeed_id_repository.py @@ -9,7 +9,9 @@ def __init__(self, default_agent_base_path: str): def write(self, datafeed_id: str, agent_base_path=None): self.datafeed_id = datafeed_id - self.agent_base_path = self.default_agent_base_path if agent_base_path is None else agent_base_path + self.agent_base_path = ( + self.default_agent_base_path if agent_base_path is None else agent_base_path + ) def read(self): return self.datafeed_id diff --git a/tests/ext/group_test.py b/tests/ext/group_test.py index 4895ed7b..2632ab48 100644 --- a/tests/ext/group_test.py +++ b/tests/ext/group_test.py @@ -1,4 +1,4 @@ -from unittest.mock import Mock, AsyncMock, MagicMock +from unittest.mock import AsyncMock, MagicMock, Mock import pytest @@ -7,8 +7,8 @@ from symphony.bdk.core.client.api_client_factory import ApiClientFactory from symphony.bdk.core.config.model.bdk_retry_config import BdkRetryConfig from symphony.bdk.core.service.user.user_util import extract_tenant_id -from symphony.bdk.ext.group import SymphonyGroupBdkExtension, SymphonyGroupService, OAuthSession -from symphony.bdk.gen import ApiClient, Configuration, ApiException +from symphony.bdk.ext.group import OAuthSession, SymphonyGroupBdkExtension, SymphonyGroupService +from symphony.bdk.gen import ApiClient, ApiException, Configuration from symphony.bdk.gen.group_model.add_member import AddMember from symphony.bdk.gen.group_model.create_group import CreateGroup from symphony.bdk.gen.group_model.group_list import GroupList @@ -67,10 +67,12 @@ def fixture_retry_config(): def fixture_group_service(api_client_factory, auth_session, retry_config): return SymphonyGroupService(api_client_factory, auth_session, retry_config) + @pytest.fixture(name="mocked_group") def fixture_group(): return ReadGroup(type="SDL", owner_type=Owner(value="TENANT"), owner_id=123, name="SDl test") + def assert_called_idm_tokens(first_call_args, session_token=SESSION_TOKEN): assert first_call_args.args[0] == "/idm/tokens" params = dict(first_call_args.args[3]) @@ -97,7 +99,9 @@ def test_group_extension_initialisation(): async def test_insert_group(group_service, mocked_group, api_client): api_client.call_api.return_value = mocked_group - create_group = CreateGroup(type="SDL", owner_type=Owner(value="TENANT"), owner_id=190, name="SDL") + create_group = CreateGroup( + type="SDL", owner_type=Owner(value="TENANT"), owner_id=190, name="SDL" + ) group = await group_service.insert_group(create_group) assert group.type == mocked_group.type @@ -116,26 +120,31 @@ async def test_list_groups(group_service, mocked_group, api_client): api_client.call_api.assert_called_once() assert api_client.call_api.call_args.args[0] == "/v1/groups/type/{typeId}" + @pytest.mark.asyncio async def test_list_all_groups(group_service, api_client): - api_client.call_api.return_value = \ - get_deserialized_object_from_resource(GroupList, "group/list_all_groups_one_page.json") + api_client.call_api.return_value = get_deserialized_object_from_resource( + GroupList, "group/list_all_groups_one_page.json" + ) gen = await group_service.list_all_groups(chunk_size=2) groups = [d async for d in gen] args, kwargs = api_client.call_api.call_args - assert args[0] == '/v1/groups/type/{typeId}' - assert args[3] == [('limit', 2)] + assert args[0] == "/v1/groups/type/{typeId}" + assert args[3] == [("limit", 2)] assert len(groups) == 2 - assert groups[0]['name'] == 'SDl test 0' - assert groups[1]['name'] == 'SDl test 1' + assert groups[0]["name"] == "SDl test 0" + assert groups[1]["name"] == "SDl test 1" + @pytest.mark.asyncio async def test_list_all_groups_2_pages(group_service, api_client): - return_values = [get_deserialized_object_from_resource(GroupList, "group/list_all_groups_page_1.json"), - get_deserialized_object_from_resource(GroupList, "group/list_all_groups_page_2.json")] + return_values = [ + get_deserialized_object_from_resource(GroupList, "group/list_all_groups_page_1.json"), + get_deserialized_object_from_resource(GroupList, "group/list_all_groups_page_2.json"), + ] api_client.call_api.side_effect = return_values @@ -145,21 +154,27 @@ async def test_list_all_groups_2_pages(group_service, api_client): args, kwargs = api_client.call_api.call_args assert api_client.call_api.call_count == 2 - assert args[0] == '/v1/groups/type/{typeId}' - assert dict(args[3])['after'] == '2' - assert dict(args[3])['limit'] == 2 + assert args[0] == "/v1/groups/type/{typeId}" + assert dict(args[3])["after"] == "2" + assert dict(args[3])["limit"] == 2 assert len(groups) == 4 - assert groups[0]['name'] == 'SDl test 0' - assert groups[1]['name'] == 'SDl test 1' - assert groups[2]['name'] == 'SDl test 2' - assert groups[3]['name'] == 'SDl test 3' + assert groups[0]["name"] == "SDl test 0" + assert groups[1]["name"] == "SDl test 1" + assert groups[2]["name"] == "SDl test 2" + assert groups[3]["name"] == "SDl test 3" + @pytest.mark.asyncio async def test_list_groups_with_params(group_service, mocked_group, api_client): api_client.call_api.return_value = GroupList(data=[mocked_group]) - groups = await group_service.list_groups(status=Status(value="ACTIVE"), before="0", after="50", limit=50, - sort_order=SortOrder(value="ASC")) + groups = await group_service.list_groups( + status=Status(value="ACTIVE"), + before="0", + after="50", + limit=50, + sort_order=SortOrder(value="ASC"), + ) assert len(groups.data) == 1 api_client.call_api.assert_called_once() assert api_client.call_api.call_args.args[0] == "/v1/groups/type/{typeId}" @@ -178,11 +193,18 @@ async def test_update_group(group_service, mocked_group, api_client): mocked_group.id = "group_id" api_client.call_api.return_value = mocked_group - update_group = UpdateGroup(name="Updated name", type=mocked_group.type, owner_type=Owner(value="TENANT"), - owner_id=mocked_group.owner_id, id=mocked_group.id, e_tag=mocked_group.e_tag, - status=Status(value="ACTIVE")) - group = await group_service.update_group(if_match=mocked_group.e_tag, group_id=mocked_group.id, - update_group=update_group) + update_group = UpdateGroup( + name="Updated name", + type=mocked_group.type, + owner_type=Owner(value="TENANT"), + owner_id=mocked_group.owner_id, + id=mocked_group.id, + e_tag=mocked_group.e_tag, + status=Status(value="ACTIVE"), + ) + group = await group_service.update_group( + if_match=mocked_group.e_tag, group_id=mocked_group.id, update_group=update_group + ) assert group.name == mocked_group.name api_client.call_api.assert_called_once() @@ -226,12 +248,15 @@ async def test_add_member_to_group(group_service, mocked_group, api_client): assert group.name == mocked_group.name api_client.call_api.assert_called_once() assert api_client.call_api.call_args.args[0] == "/v1/groups/{groupId}/member" - assert api_client.call_api.call_args.kwargs["body"] == AddMember(member=Member( - member_id=user_id, member_tenant=extract_tenant_id(user_id))) + assert api_client.call_api.call_args.kwargs["body"] == AddMember( + member=Member(member_id=user_id, member_tenant=extract_tenant_id(user_id)) + ) @pytest.mark.asyncio -async def test_add_member_to_group_with_retries(group_service, mocked_group, api_client, login_client): +async def test_add_member_to_group_with_retries( + group_service, mocked_group, api_client, login_client +): login_client.call_api.return_value = JwtToken(access_token="access token") mocked_group.id = "group_id" @@ -244,8 +269,9 @@ async def test_add_member_to_group_with_retries(group_service, mocked_group, api assert group.name == mocked_group.name assert api_client.call_api.call_count == 2 assert api_client.call_api.call_args.args[0] == "/v1/groups/{groupId}/member" - assert api_client.call_api.call_args.kwargs["body"] == AddMember(member=Member( - member_id=user_id, member_tenant=extract_tenant_id(user_id))) + assert api_client.call_api.call_args.kwargs["body"] == AddMember( + member=Member(member_id=user_id, member_tenant=extract_tenant_id(user_id)) + ) @pytest.mark.asyncio @@ -275,7 +301,10 @@ async def test_oauth_session_refresh(auth_session, retry_config, login_client): @pytest.mark.asyncio async def test_oauth_session_refresh_with_retries(auth_session, retry_config, login_client): bearer_token = "bearer token" - login_client.call_api.side_effect = [ApiException(status=401), JwtToken(access_token=bearer_token)] + login_client.call_api.side_effect = [ + ApiException(status=401), + JwtToken(access_token=bearer_token), + ] authenticator = AsyncMock(BotAuthenticator) auth_session._authenticator = authenticator updated_session_token = "updated session token" @@ -299,15 +328,21 @@ async def test_oauth_settings(auth_session, retry_config, login_client): oauth_session = OAuthSession(login_client, auth_session, retry_config) settings = await oauth_session.get_auth_settings() - assert settings == {"bearerAuth": {"in": "header", - "key": "Authorization", - "type": "bearer", - "value": "Bearer bearer token"}} + assert settings == { + "bearerAuth": { + "in": "header", + "key": "Authorization", + "type": "bearer", + "value": "Bearer bearer token", + } + } login_client.call_api.assert_called_once() @pytest.mark.asyncio -async def test_oauth_settings_does_not_refresh_when_called_twice(auth_session, retry_config, login_client): +async def test_oauth_settings_does_not_refresh_when_called_twice( + auth_session, retry_config, login_client +): bearer_token = "bearer token" token = JwtToken(access_token=bearer_token) login_client.call_api.return_value = token @@ -316,8 +351,12 @@ async def test_oauth_settings_does_not_refresh_when_called_twice(auth_session, r await oauth_session.get_auth_settings() settings = await oauth_session.get_auth_settings() - assert settings == {"bearerAuth": {"in": "header", - "key": "Authorization", - "type": "bearer", - "value": "Bearer bearer token"}} + assert settings == { + "bearerAuth": { + "in": "header", + "key": "Authorization", + "type": "bearer", + "value": "Bearer bearer token", + } + } login_client.call_api.assert_called_once() diff --git a/tests/utils/resource_utils.py b/tests/utils/resource_utils.py index 494ba956..44e03a77 100644 --- a/tests/utils/resource_utils.py +++ b/tests/utils/resource_utils.py @@ -1,5 +1,5 @@ -from pathlib import Path import json +from pathlib import Path from symphony.bdk.gen.api_client import validate_and_convert_types from symphony.bdk.gen.configuration import Configuration @@ -34,12 +34,14 @@ def deserialize_object(model, payload): test_data = json.loads(payload) except json.JSONDecodeError: test_data = payload - return validate_and_convert_types(input_value=test_data, - required_types_mixed=(model,), - path_to_item=["test_data"], - spec_property_naming=True, - _check_type=True, - configuration=Configuration(discard_unknown_keys=True)) + return validate_and_convert_types( + input_value=test_data, + required_types_mixed=(model,), + path_to_item=["test_data"], + spec_property_naming=True, + _check_type=True, + configuration=Configuration(discard_unknown_keys=True), + ) def get_config_resource_filepath(relative_path):