Skip to content

feat: added output management integration#161

Open
Venkatakrishnan774 wants to merge 15 commits into
SAP:mainfrom
Venkatakrishnan774:sap_om_poc
Open

feat: added output management integration#161
Venkatakrishnan774 wants to merge 15 commits into
SAP:mainfrom
Venkatakrishnan774:sap_om_poc

Conversation

@Venkatakrishnan774

@Venkatakrishnan774 Venkatakrishnan774 commented Jun 14, 2026

Copy link
Copy Markdown

Disclaimer: Do not include SAP-internal or customer-specific information in this PR (e.g. internal system URLs, customer names, tenant IDs, or confidential configurations). This is a public repository.

Description

Output Management is a BTP shared service which has features like sending documents to external system in various channels like EDI/EMAIL/Print. As part of this, we are mainly concentrating on the email use case where one can send email via our framework along with attachments if necessary.

We are exposing capabilities via util methods of sending email as BTP destination based and via integrated MCP hub. When using the MCP Hub way, we are giving the capability to construct the required payload and invoke the util functions. When using the BTP destination way, we are giving the options to input the destination details and the payloads.

Related Issue

Closes #<issue_number>

(Link to the GitHub issue this PR addresses)

Type of Change

Please check the relevant option:

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Code refactoring
  • Dependency update

How to Test

Describe how reviewers can test your changes:

  1. Step 1
  2. Step 2
  3. Expected result

Checklist

Before submitting your PR, please review and check the following:

  • I have read the Contributing Guidelines
  • I have verified that my changes solve the issue
  • I have added/updated automated tests to cover my changes
  • All tests pass locally
  • I have verified that my code follows the Code Guidelines
  • I have updated documentation (if applicable)
  • I have added type hints for all public APIs
  • My code does not contain sensitive information (credentials, tokens, etc.)
  • I have followed Conventional Commits for commit messages

Breaking Changes

If this PR introduces breaking changes, please describe:

  • What breaks
  • Migration path for users
  • Alternative approaches considered

Additional Notes

Add any additional context, screenshots, or information that would help reviewers.

@Venkatakrishnan774 Venkatakrishnan774 requested a review from a team as a code owner June 14, 2026 12:21
@cla-assistant

cla-assistant Bot commented Jun 14, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ Venkatakrishnan774
❌ lokeshkumarkuntal
You have signed the CLA already but the status is still pending? Let us recheck it.

@betinacosta

Copy link
Copy Markdown
Member

Hi. Could you give a more detailed description of this new feature for future reference? Also, is necessary that the title follows commit convention, for exemple: feat: added output management integration.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests seems to be failing. Could you fix it?

self,
http_session: requests.Session,
base_url: str,
destination: any = None,

@betinacosta betinacosta Jun 18, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these parameters are optional, please use:

destination: Optional[any] = None

or even

destination: Optional[Destination] = None

http_session: requests.Session,
base_url: str,
destination: any = None,
destination_instance: str = None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destination_instance: Optional[str] = None

def __init__(
self,
base_url: str,
destination: any = None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destination: Optional[any] = None

or even

destination: Optional[Destination] = None

self,
base_url: str,
destination: any = None,
destination_instance: str = None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destination_instance: Optional[str] = None


return None

def _execute_request(self, method: str, endpoint: str, **kwargs):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing return type annotation.

@Venkatakrishnan774 Venkatakrishnan774 changed the title Added output management integration feat: added output management integration Jun 18, 2026
self._destination_instance = destination_instance

# Get sender-provider-subaccount-id from environment variable
self._sender_provider_subaccount_id = os.getenv("APPFND_CONHOS_SUBACCOUNTID")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are now using CLOUD_SDK_CFG_ as prefix. You can algo take a look at src/sap_cloud_sdk/core/secret_resolver, it may help

# Get the destination object - it handles authentication automatically
http_destination = self._destination_credential_config.get_destination()

# Get the base URL from destinatiozxn

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo


# If no ID found, use template key as fallback
if not doc_id:
doc_id = f"{notification_template_key}-{id(business_document)}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use id() as fallback can not be a good choice, once it is not stable across runs

# Build attachment config if URLs are provided
attachment_config = None
if attachment_urls:
from ..models.attachment_config import AttachmentConfig

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lazy imports seems to serve no purpose here. There is no circular dependency risk here.

from sap_cloud_sdk.destination import create_certificate_client, AccessStrategy
import tempfile
import base64
from cryptography.hazmat.primitives.serialization import pkcs12, Encoding, PrivateFormat, NoEncryption

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hides an undeclared runtime dependency from static analysis tools (ruff, ty), prevents the type checker from seeing the symbols, and means the ImportError is only discovered at call time rather than at startup.

import logging
from typing import Dict, Optional
import os
import json

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used?

CONTENT_TYPE_PDF = "application/pdf"


class Status(Enum):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the values be lowercased?

CANCELLED = "CANCELLED"


class FileFormat(Enum):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the values be lowercased?

XML = "XML"


class Channel(Enum):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the values be lowercased?

API_OUTPUT_CONTROL = "/api/output-control-api/v1/"

# Headers
CONTENT_TYPE = "Content-Type"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why CONTENT_TYPE and HEADER_CONTENT_TYPE have the same value?


# Headers
CONTENT_TYPE = "Content-Type"
APPLICATION_JSON = "application/json"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same goes for APPLICATION_JSON and CONTENT_TYPE_JSON.


return None

def _execute_request(self, method: str, endpoint: str, **kwargs):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, is this method necessary? Its a wrapper for just one line.

destination_name: str,
cc: Optional[List[str]] = None,
template_language: str = "en",
access_strategy: str = "PROVIDER_ONLY",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

access_strategy: Optional[str] = "PROVIDER_ONLY"

business_document: Dict[str, Any],
destination_name: str,
cc: Optional[List[str]] = None,
template_language: str = "en",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

template_language: Optional[str] = "en"


try:
request_body = output_request.model_dump(by_alias=True, exclude_none=True)
logger.info(f"Request body: {request_body}")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we log the request body? I believe it may have sensitive information.

@Venkatakrishnan774

Copy link
Copy Markdown
Author

Hi. Could you give a more detailed description of this new feature for future reference? Also, is necessary that the title follows commit convention, for exemple: feat: added output management integration.

Done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants