[Python] REPLIT - API CONNECTOR to CONNECTED APP IN SALESFORCE (sandbox)

Discussão em 'Python' iniciado por Stack, Fevereiro 20, 2025.

  1. Stack

    Stack Membro Participativo

    Hello Wyour textord!

    I created in Replit an API connector to connect my App to Salesforce (sandbox) Connected App.

    I'm using OAuth JWT flow with:

    • Consumer Key (SF_CONSUMER_KEY)
    • Consumer Secret (SF_CONSUMER_SECRET)
    • Instance URL (SF_INSTANCE_URL)

    The errors.

    test_salesforce.py::test_connection FAILED [ 20%] test_salesforce.py::test_fetch_data FAILED [ 40%] test_salesforce.py::test_create_record FAILED [ 60%] test_salesforce.py::test_batch_create FAILED [ 80%] test_salesforce.py::test_rate_limiting FAILED [100%]

    ========================================= FAILURES ========================================= _____________________________________ test_connection ______________________________________

    self = <api_connectors.SalesforceConnector object at 0x7f9b5b8b4e50>

    async def connect(self) -> bool:
    for attempt in range(self.MAX_RETRIES):
    try:
    from simple_salesforce import Salesforce
    import logging

    logging.info(f"Attempting Salesforce connection - attempt {attempt + 1}")
    username = self.credentials.get('username') or os.getenv('SF_USERNAME')
    password = self.credentials.get('password') or os.getenv('SF_PASSWORD')
    security_token = self.credentials.get('security_token') or os.getenv('SF_SECURITY_TOKEN')
    consumer_key = self.credentials.get('consumer_key') or os.getenv('SF_CONSUMER_KEY')
    consumer_secret = self.credentials.get('consumer_secret') or os.getenv('SF_CONSUMER_SECRET')

    if not all([username, password, security_token]):
    raise SalesforceError("Missing required credentials: username, password, or security token")

    > self.client = Salesforce(
    username=username,
    password=password,
    security_token=security_token,
    consumer_key=consumer_key,
    consumer_secret=consumer_secret,
    domain='test' # Specifically for developer/sandbox environments
    )

    api_connectors.py:54:
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    .pythonlibs/lib/python3.11/site-packages/simple_salesforce/api.py:153: in __init__
    self._refresh_session()
    .pythonlibs/lib/python3.11/site-packages/simple_salesforce/api.py:307: in _refresh_session
    self.session_id, self.sf_instance = self._salesforce_login_partial()
    .pythonlibs/lib/python3.11/site-packages/simple_salesforce/login.py:227: in SalesforceLogin
    return soap_login(soap_url, login_soap_request_body,


    soap_url = 'https://test.salesforce.com/services/Soap/u/59.0'
    request_body = '<?xml version="1.0" encoding="utf-8" ?>\n<env:Envelope\n xmlns:xsd="http://www.w3.org/2001/XMLSchema"\n ...assword>Man-Top-Ata.123!uC70ArGo6ATwhysrY4ETlukrI</n1:password>\n </n1:login>\n </env:Body>\n</env:Envelope>'
    headers = {'SOAPAction': 'login', 'charset': 'UTF-8', 'content-type': 'text/xml'}
    proxies = {}, session = <requests.sessions.Session object at 0x7f9b5cb07710>

    def soap_login(
    soap_url: str,
    request_body: str,
    headers: Optional[Headers],
    proxies: Optional[Proxies],
    session: Optional[requests.Session] = None) -> Tuple[str, str]:
    """Process SOAP specific login workflow."""
    response = (session or requests).post(
    soap_url, request_body, headers=headers, proxies=proxies)

    if response.status_code != 200:
    except_code: Union[str, int, None]
    except_msg: str
    try:
    except_code = getUniqueElementValueFromXmlString(
    response.content, 'sf:exceptionCode')
    except_msg = (getUniqueElementValueFromXmlString(
    response.content, 'sf:exceptionMessage')
    or response.content.decode())
    except ExpatError:
    except_code = response.status_code
    except_msg = response.content.decode()
    > raise SalesforceAuthenticationFailed(except_code, except_msg)
    E simple_salesforce.exceptions.SalesforceAuthenticationFailed: INVALID_LOGIN: Invalid username, password, security token; or user locked out.

    .pythonlibs/lib/python3.11/site-packages/simple_salesforce/login.py:253: SalesforceAuthenticationFailed

    The above exception was the direct cause of the following exception:

    connector = <api_connectors.SalesforceConnector object at 0x7f9b5b8b4e50>

    @pytest.mark.asyncio
    async def test_connection(connector):
    > success = await connector.connect()

    test_salesforce.py:41:


    self = <api_connectors.SalesforceConnector object at 0x7f9b5b8b4e50>

    async def connect(self) -> bool:
    for attempt in range(self.MAX_RETRIES):
    try:
    from simple_salesforce import Salesforce
    import logging

    logging.info(f"Attempting Salesforce connection - attempt {attempt + 1}")
    username = self.credentials.get('username') or os.getenv('SF_USERNAME')
    password = self.credentials.get('password') or os.getenv('SF_PASSWORD')
    security_token = self.credentials.get('security_token') or os.getenv('SF_SECURITY_TOKEN')
    consumer_key = self.credentials.get('consumer_key') or os.getenv('SF_CONSUMER_KEY')
    consumer_secret = self.credentials.get('consumer_secret') or os.getenv('SF_CONSUMER_SECRET')

    if not all([username, password, security_token]):
    raise SalesforceError("Missing required credentials: username, password, or security token")

    self.client = Salesforce(
    username=username,
    password=password,
    security_token=security_token,
    consumer_key=consumer_key,
    consumer_secret=consumer_secret,
    domain='test' # Specifically for developer/sandbox environments
    )
    logging.info("Successfully connected to Salesforce")
    return True
    except Exception as e:
    logging.error(f"Salesforce connection error: {str(e)}")
    if attempt == self.MAX_RETRIES - 1:
    > raise SalesforceError(f"Failed to connect after {self.MAX_RETRIES} attempts") from e
    E api_connectors.SalesforceError: Failed to connect after 3 attempts

    api_connectors.py:67: SalesforceError


    ------------------------------------ Captured log call ------------------------------------- ERROR root:api_connectors.py:65 Salesforce connection error: INVALID_LOGIN: Invalid username, password, security token; or user locked out. ERROR root:api_connectors.py:65 Salesforce connection error: INVALID_LOGIN: Invalid username, password, security token; or user locked out. ERROR root:api_connectors.py:65 Salesforce connection error: INVALID_LOGIN: Invalid username, password, security token; or user locked out.

    =========================== short test summary info ==================================

    FAILED test_salesforce.py::test_connection - api_connectors.SalesforceError: Failed to connect after 3 attempts FAILED test_salesforce.py::test_fetch_data - api_connectors.SalesforceError: Failed to connect after 3 attempts FAILED test_salesforce.py::test_create_record - api_connectors.SalesforceError: Failed to connect after 3 attempts FAILED test_salesforce.py::test_batch_create - api_connectors.SalesforceError: Failed to connect after 3 attempts FAILED test_salesforce.py::test_rate_limiting - api_connectors.SalesforceError: Failed to connect after 3 attempts

    This is my api_connector.py:

    from abc import ABC, abstractmethod
    from typing import Dict, Any, List
    from models import Department
    import asyncio
    import time
    import logging
    import os
    from datetime import datetime

    class BaseAPIConnector(ABC):
    def __init__(self, credentials: Dict[str, str], department: Department):
    self.credentials = credentials
    self.department = department

    @abstractmethod
    async def connect(self) -> bool:
    pass

    @abstractmethod
    async def fetch_data(self, query: str) -> Dict[str, Any]:
    pass

    class SalesforceError(Exception):
    """Base exception for Salesforce operations"""
    pass

    class SalesforceConnector(BaseAPIConnector):
    MAX_RETRIES = 3
    BATCH_SIZE = 200
    API_LIMIT_PER_SECOND = 20

    def __init__(self, credentials: Dict[str, str], department: Department):
    super().__init__(credentials, department)
    self._last_request_time = 0
    self._request_count = 0

    async def connect(self) -> bool:
    for attempt in range(self.MAX_RETRIES):
    try:
    from simple_salesforce import Salesforce
    import logging

    logging.info(f"Attempting Salesforce connection - attempt {attempt + 1}")
    username = self.credentials.get('username') or os.getenv('SF_USERNAME')
    password = self.credentials.get('password') or os.getenv('SF_PASSWORD')
    security_token = self.credentials.get('security_token') or os.getenv('SF_SECURITY_TOKEN')
    consumer_key = self.credentials.get('consumer_key') or os.getenv('SF_CONSUMER_KEY')
    consumer_secret = self.credentials.get('consumer_secret') or os.getenv('SF_CONSUMER_SECRET')

    if not all([username, password, security_token]):
    raise SalesforceError("Missing required credentials: username, password, or security token")

    self.client = Salesforce(
    username=username,
    password=password,
    security_token=security_token,
    consumer_key=consumer_key,
    consumer_secret=consumer_secret,
    domain='test' # Specifically for developer/sandbox environments
    )
    logging.info("Successfully connected to Salesforce")
    return True
    except Exception as e:
    logging.error(f"Salesforce connection error: {str(e)}")
    if attempt == self.MAX_RETRIES - 1:
    raise SalesforceError(f"Failed to connect after {self.MAX_RETRIES} attempts") from e
    await asyncio.sleep(2 ** attempt) # Exponential backoff
    return False

    async def fetch_data(self, query: str) -> Dict[str, Any]:
    """Execute SOQL query"""
    try:
    return self.client.query(query)
    except Exception as e:
    print(f"Salesforce query error: {str(e)}")
    raise

    async def _rate_limit(self):
    """Implement rate limiting for API calls"""
    current_time = time.time()
    if current_time - self._last_request_time < 1: # Within 1 second window
    if self._request_count >= self.API_LIMIT_PER_SECOND:
    await asyncio.sleep(1 - (current_time - self._last_request_time))
    self._request_count = 0
    self._last_request_time = time.time()
    else:
    self._request_count = 0
    self._last_request_time = current_time
    self._request_count += 1

    async def create_record(self, sobject: str, data: Dict[str, Any]) -> Dict[str, Any]:
    """Create a new Salesforce record"""
    await self._rate_limit()
    try:
    logging.info(f"Creating new {sobject} record")
    sf_object = getattr(self.client, sobject)
    result = sf_object.create(data)
    logging.info(f"Successfully created {sobject} record")
    return result
    except Exception as e:
    logging.error(f"Salesforce create error: {str(e)}")
    raise SalesforceError(f"Failed to create {sobject} record") from e

    async def create_records_batch(self, sobject: str, records: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """Create multiple records in batches"""
    results = []
    for i in range(0, len(records), self.BATCH_SIZE):
    batch = records[i:i + self.BATCH_SIZE]
    await self._rate_limit()
    try:
    sf_object = getattr(self.client, sobject)
    batch_results = sf_object.bulk.create(batch)
    results.extend(batch_results)
    logging.info(f"Successfully created batch of {len(batch)} {sobject} records")
    except Exception as e:
    logging.error(f"Batch creation error: {str(e)}")
    raise SalesforceError(f"Failed to create batch of {sobject} records") from e
    return results

    async def update_record(self, sobject: str, record_id: str, data: Dict[str, Any]) -> Dict[str, Any]:
    """Update an existing Salesforce record"""
    try:
    sf_object = getattr(self.client, sobject)
    return sf_object.update(record_id, data)
    except Exception as e:
    print(f"Salesforce update error: {str(e)}")
    raise

    async def delete_record(self, sobject: str, record_id: str) -> bool:
    """Delete a Salesforce record"""
    try:
    sf_object = getattr(self.client, sobject)
    return sf_object.delete(record_id)
    except Exception as e:
    print(f"Salesforce delete error: {str(e)}")
    raise

    async def describe_object(self, sobject: str) -> Dict[str, Any]:
    """Get metadata about a Salesforce object"""
    try:
    sf_object = getattr(self.client, sobject)
    return sf_object.describe()
    except Exception as e:
    print(f"Salesforce describe error: {str(e)}")
    raise

    async def get_deleted_records(self, sobject: str, start: datetime, end: datetime) -> Dict[str, Any]:
    """Get records deleted between start and end dates"""
    try:
    sf_object = getattr(self.client, sobject)
    return sf_object.deleted(start, end)
    except Exception as e:
    print(f"Salesforce get deleted records error: {str(e)}")
    raise

    async def get_updated_records(self, sobject: str, start: datetime, end: datetime) -> Dict[str, Any]:
    """Get records updated between start and end dates"""
    try:
    sf_object = getattr(self.client, sobject)
    return sf_object.updated(start, end)
    except Exception as e:
    print(f"Salesforce get updated records error: {str(e)}")
    raise

    class DynamicsConnector(BaseAPIConnector):
    async def connect(self) -> bool:
    try:
    # Implement Dynamics connection logic
    return True
    except Exception as e:
    print(f"Dynamics connection error: {str(e)}")
    return False

    async def fetch_data(self, query: str) -> Dict[str, Any]:
    # Implement Dynamics query execution
    pass

    class QlikConnector(BaseAPIConnector):
    async def connect(self) -> bool:
    try:
    # Implement Qlik connection logic
    return True
    except Exception as e:
    print(f"Qlik connection error: {str(e)}")
    return False

    async def fetch_data(self, query: str) -> Dict[str, Any]:
    # Implement Qlik query execution
    pass


    This is my test_salesforce.py

    import pytest
    import asyncio
    import time
    import os
    from unittest.mock import Mock, patch, MagicMock
    from api_connectors import SalesforceConnector, SalesforceError
    from models import Department

    @pytest.fixture
    def test_credentials():
    return {
    "username": os.getenv('SF_USERNAME'),
    "password": os.getenv('SF_PASSWORD'),
    "security_token": os.getenv('SF_SECURITY_TOKEN'),
    "consumer_key": os.getenv('SF_CONSUMER_KEY'),
    "consumer_secret": os.getenv('SF_CONSUMER_SECRET')
    }

    @pytest.fixture
    def mock_sf():
    mock = MagicMock()
    mock.query.return_value = {'records': []}
    mock.Account = MagicMock()
    mock.Account.create.return_value = {'success': True, 'id': '001xxx'}
    mock.Account.bulk = MagicMock()
    mock.Account.bulk.create.return_value = [{'success': True} for _ in range(5)]
    return mock

    @pytest.fixture
    def connector(test_credentials):
    with patch('simple_salesforce.Salesforce') as mock_sf_class:
    mock_sf_instance = MagicMock()
    mock_sf_class.return_value = mock_sf_instance
    mock_sf_instance.query.return_value = {'records': []}
    connector = SalesforceConnector(test_credentials, Department.MARKETING)
    return connector

    @pytest.mark.asyncio
    async def test_connection(connector):
    success = await connector.connect()
    assert success == True

    @pytest.mark.asyncio
    async def test_fetch_data(connector):
    await connector.connect()
    result = await connector.fetch_data("SELECT Id, Name FROM Account LIMIT 5")
    assert 'records' in result

    @pytest.mark.asyncio
    async def test_create_record(connector):
    await connector.connect()
    data = {"Name": "Test Account", "Description": "Test Description"}
    result = await connector.create_record("Account", data)
    assert result['success'] == True

    @pytest.mark.asyncio
    async def test_batch_create(connector):
    await connector.connect()
    records = [
    {"Name": f"Test Account {i}", "Description": "Batch Test"}
    for i in range(5)
    ]
    results = await connector.create_records_batch("Account", records)
    assert len(results) == 5

    @pytest.mark.asyncio
    async def test_rate_limiting(connector):
    await connector.connect()
    start_time = time.time()
    requests = [connector._rate_limit() for _ in range(25)]
    await asyncio.gather(*requests)
    duration = time.time() - start_time
    assert duration >= 1 # Should take at least 1 second due to rate limiting

    Continue reading...

Compartilhe esta Página