salespyforce 1.3.0rc3__tar.gz → 1.4.0.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {salespyforce-1.3.0rc3/src/salespyforce.egg-info → salespyforce-1.4.0.dev1}/PKG-INFO +46 -23
  2. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/README.md +5 -1
  3. salespyforce-1.4.0.dev1/pyproject.toml +62 -0
  4. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/core.py +35 -11
  5. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/log_utils.py +10 -9
  6. salespyforce-1.4.0.dev1/src/salespyforce/utils/tests/conftest.py +202 -0
  7. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/tests/resources.py +2 -2
  8. salespyforce-1.4.0.dev1/src/salespyforce/utils/tests/test_core_utils.py +195 -0
  9. salespyforce-1.4.0.dev1/src/salespyforce/utils/tests/test_instantiate_object.py +62 -0
  10. salespyforce-1.4.0.dev1/src/salespyforce/utils/tests/test_log_utils.py +79 -0
  11. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/tests/test_sobjects.py +22 -15
  12. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/tests/test_soql.py +8 -8
  13. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/tests/test_sosl.py +9 -9
  14. salespyforce-1.4.0.dev1/src/salespyforce/utils/tests/test_version_utils.py +70 -0
  15. salespyforce-1.4.0.dev1/src/salespyforce/utils/version.py +52 -0
  16. salespyforce-1.3.0rc3/PKG-INFO +0 -234
  17. salespyforce-1.3.0rc3/pyproject.toml +0 -20
  18. salespyforce-1.3.0rc3/setup.cfg +0 -4
  19. salespyforce-1.3.0rc3/setup.py +0 -89
  20. salespyforce-1.3.0rc3/src/salespyforce/utils/tests/test_instantiate_object.py +0 -49
  21. salespyforce-1.3.0rc3/src/salespyforce/utils/version.py +0 -26
  22. salespyforce-1.3.0rc3/src/salespyforce.egg-info/SOURCES.txt +0 -28
  23. salespyforce-1.3.0rc3/src/salespyforce.egg-info/dependency_links.txt +0 -1
  24. salespyforce-1.3.0rc3/src/salespyforce.egg-info/requires.txt +0 -13
  25. salespyforce-1.3.0rc3/src/salespyforce.egg-info/top_level.txt +0 -1
  26. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/LICENSE +0 -0
  27. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/__init__.py +0 -0
  28. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/api.py +0 -0
  29. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/chatter.py +0 -0
  30. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/errors/__init__.py +0 -0
  31. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/errors/exceptions.py +0 -0
  32. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/errors/handlers.py +0 -0
  33. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/knowledge.py +0 -0
  34. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/__init__.py +0 -0
  35. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/core_utils.py +0 -0
  36. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/helper.py +0 -0
  37. {salespyforce-1.3.0rc3 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/tests/__init__.py +0 -0
@@ -1,33 +1,51 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: salespyforce
3
- Version: 1.3.0rc3
3
+ Version: 1.4.0.dev1
4
4
  Summary: A Python toolset for performing Salesforce API calls
5
- Home-page: https://github.com/jeffshurtliff/salespyforce
5
+ License: MIT License
6
+
7
+ Copyright (c) 2023 Jeff Shurtliff
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+ Keywords: salesforce,sfdc,api,rest,tooling,python
6
27
  Author: Jeff Shurtliff
7
- Author-email: jeff.shurtliff@rsa.com
8
- Project-URL: Issue Tracker, https://github.com/jeffshurtliff/salespyforce/issues
9
- Classifier: Development Status :: 3 - Alpha
10
- Classifier: Environment :: Web Environment
11
- Classifier: Intended Audience :: Information Technology
12
- Classifier: Intended Audience :: System Administrators
28
+ Author-email: jeffshurtliff@gmail.com
29
+ Requires-Python: >=3.9,<4.0
30
+ Classifier: Development Status :: 4 - Beta
31
+ Classifier: Intended Audience :: Developers
13
32
  Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Natural Language :: English
15
- Classifier: Operating System :: OS Independent
16
33
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.6
18
- Classifier: Programming Language :: Python :: 3.7
19
- Classifier: Programming Language :: Python :: 3.8
34
+ Classifier: Programming Language :: Python :: 3 :: Only
20
35
  Classifier: Programming Language :: Python :: 3.9
21
36
  Classifier: Programming Language :: Python :: 3.10
22
- Classifier: Topic :: Communications
23
- Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Content Management System
24
- Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
25
- Classifier: Topic :: Office/Business
26
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
- Requires-Python: >=3.6
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Programming Language :: Python :: 3.13
40
+ Classifier: Topic :: Software Development :: Libraries
41
+ Classifier: Topic :: Internet :: WWW/HTTP
42
+ Requires-Dist: PyYAML (>=6.0.3,<7)
43
+ Requires-Dist: requests (>=2.32.5)
44
+ Project-URL: Documentation, https://salespyforce.readthedocs.io/en/latest/
45
+ Project-URL: Homepage, https://github.com/jeffshurtliff/salespyforce
46
+ Project-URL: Issue Tracker, https://github.com/jeffshurtliff/salespyforce/issues
47
+ Project-URL: Repository, https://github.com/jeffshurtliff/salespyforce
28
48
  Description-Content-Type: text/markdown
29
- Provides-Extra: sphinx
30
- License-File: LICENSE
31
49
 
32
50
  # salespyforce
33
51
  A Python toolset for performing Salesforce API calls
@@ -45,7 +63,7 @@ A Python toolset for performing Salesforce API calls
45
63
  <td>Latest Beta/RC Release</td>
46
64
  <td>
47
65
  <a href='https://pypi.org/project/salespyforce/#history'>
48
- <img alt="PyPI" src="https://img.shields.io/badge/pypi-1.3.0rc2-blue">
66
+ <img alt="PyPI" src="https://img.shields.io/badge/pypi-1.4.0.dev1-blue">
49
67
  </a>
50
68
  </td>
51
69
  </tr>
@@ -122,6 +140,10 @@ A Python toolset for performing Salesforce API calls
122
140
  </tr>
123
141
  </table>
124
142
 
143
+ ## Overview
144
+ salespyforce is a Python toolkit focused on interacting with Salesforce APIs, with a primary `Salesforce` class
145
+ that centralizes authentication, version selection, and access to helper feature sets (Chatter, Knowledge).
146
+
125
147
  ## Installation
126
148
  The package can be installed via pip using the syntax below.
127
149
 
@@ -232,3 +254,4 @@ If you would like to donate to this project then you can do so using [this PayPa
232
254
 
233
255
  ## Disclaimer
234
256
  This package is considered unofficial and is in no way endorsed or supported by [Salesforce Inc](https://www.salesforce.com).
257
+
@@ -14,7 +14,7 @@ A Python toolset for performing Salesforce API calls
14
14
  <td>Latest Beta/RC Release</td>
15
15
  <td>
16
16
  <a href='https://pypi.org/project/salespyforce/#history'>
17
- <img alt="PyPI" src="https://img.shields.io/badge/pypi-1.3.0rc2-blue">
17
+ <img alt="PyPI" src="https://img.shields.io/badge/pypi-1.4.0.dev1-blue">
18
18
  </a>
19
19
  </td>
20
20
  </tr>
@@ -91,6 +91,10 @@ A Python toolset for performing Salesforce API calls
91
91
  </tr>
92
92
  </table>
93
93
 
94
+ ## Overview
95
+ salespyforce is a Python toolkit focused on interacting with Salesforce APIs, with a primary `Salesforce` class
96
+ that centralizes authentication, version selection, and access to helper feature sets (Chatter, Knowledge).
97
+
94
98
  ## Installation
95
99
  The package can be installed via pip using the syntax below.
96
100
 
@@ -0,0 +1,62 @@
1
+ [project]
2
+ name = "salespyforce"
3
+ version = "1.4.0.dev1"
4
+ description = "A Python toolset for performing Salesforce API calls"
5
+ readme = "README.md"
6
+ requires-python = ">=3.9,<4.0"
7
+ license = { file = "LICENSE" }
8
+ authors = [
9
+ { name = "Jeff Shurtliff", email = "jeffshurtliff@gmail.com" }
10
+ ]
11
+ keywords = ["salesforce", "sfdc", "api", "rest", "tooling", "python"]
12
+
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Software Development :: Libraries",
25
+ "Topic :: Internet :: WWW/HTTP"
26
+ ]
27
+
28
+ dependencies = [
29
+ "PyYAML>=6.0.3,<7",
30
+ "requests>=2.32.5",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/jeffshurtliff/salespyforce"
35
+ Repository = "https://github.com/jeffshurtliff/salespyforce"
36
+ Documentation = "https://salespyforce.readthedocs.io/en/latest/"
37
+ "Issue Tracker" = "https://github.com/jeffshurtliff/salespyforce/issues"
38
+
39
+ # --------------------------------------------------------------------
40
+ # Poetry-specific config
41
+ # --------------------------------------------------------------------
42
+ [tool.poetry]
43
+ packages = [
44
+ { include = "salespyforce", from = "src" }
45
+ ]
46
+
47
+ include = ["LICENSE", "README.md"]
48
+
49
+ [tool.poetry.group.dev.dependencies]
50
+ pytest = "^7.2"
51
+ bandit = { version = "^1.7.8", extras = ["sarif"] }
52
+ # Sphinx = "^7.0"
53
+ # sphinxcontrib-applehelp = ">=1.0.2"
54
+ # sphinxcontrib-devhelp = ">=1.0.2"
55
+ # sphinxcontrib-htmlhelp = ">=2.0.0"
56
+ # sphinxcontrib-jsmath = ">=1.0.1"
57
+ # sphinxcontrib-qthelp = ">=1.0.3"
58
+ # sphinxcontrib-serializinghtml = ">=1.1.4"
59
+
60
+ [build-system]
61
+ requires = ["poetry-core>=1.2.0"]
62
+ build-backend = "poetry.core.masonry.api"
@@ -3,10 +3,10 @@
3
3
  :Module: salespyforce.core
4
4
  :Synopsis: This module performs the core Salesforce-related operations
5
5
  :Usage: ``from salespyforce import Salesforce``
6
- :Example: ``sfdc = Salesforce()``
6
+ :Example: ``sfdc = Salesforce(helper=helper_file_path)``
7
7
  :Created By: Jeff Shurtliff
8
8
  :Last Modified: Jeff Shurtliff
9
- :Modified Date: 22 Nov 2023
9
+ :Modified Date: 17 Nov 2025
10
10
  """
11
11
 
12
12
  import re
@@ -20,7 +20,7 @@ from .utils import core_utils, log_utils
20
20
  from .utils.helper import get_helper_settings
21
21
 
22
22
  # Define constants
23
- CURRENT_SFDC_VERSION = '58.0'
23
+ FALLBACK_SFDC_API_VERSION = '65.0' # Used if querying the org for the version fails
24
24
 
25
25
  # Initialize logging
26
26
  logger = log_utils.initialize_logging(__name__)
@@ -29,14 +29,18 @@ logger = log_utils.initialize_logging(__name__)
29
29
  class Salesforce(object):
30
30
  """This is the class for the core object leveraged in this module."""
31
31
  # Define the function that initializes the object instance (i.e. instantiates the object)
32
- def __init__(self, connection_info=None, version=CURRENT_SFDC_VERSION, base_url=None, org_id=None, username=None,
32
+ def __init__(self, connection_info=None, version=None, base_url=None, org_id=None, username=None,
33
33
  password=None, endpoint_url=None, client_id=None, client_secret=None, security_token=None, helper=None):
34
34
  """This method instantiates the core Salesforce object.
35
35
 
36
+ .. version-changed:: 1.4.0
37
+ The authorized Salesforce org is now queried to determine the latest API version to leverage unless
38
+ explicitly defined with the ``version`` parameter when instantiating the object.
39
+
36
40
  :param connection_info: The information for connecting to the Salesforce instance
37
41
  :type connection_info: dict, None
38
- :param version: The Salesforce API version to utilize (Default: ``57.0``)
39
- :type version: str
42
+ :param version: The Salesforce API version to utilize (uses latest version from org if not explicitly defined)
43
+ :type version: str, None
40
44
  :param base_url: The base URL of the Salesforce instance
41
45
  :type base_url: str, None
42
46
  :param org_id: The Org ID of the Salesforce instance
@@ -93,15 +97,15 @@ class Salesforce(object):
93
97
  # Define the base URL value
94
98
  self.base_url = self.connection_info.get('base_url')
95
99
 
96
- # Define the version value
97
- self.version = f'v{version}'
98
-
99
100
  # Define the connection response data variables
100
101
  auth_response = self.connect()
101
102
  self.access_token = auth_response.get('access_token')
102
103
  self.instance_url = auth_response.get('instance_url')
103
104
  self.signature = auth_response.get('signature')
104
105
 
106
+ # Define the version with explicitly provided version or by querying the Salesforce org
107
+ self.version = f'v{version}' if version else f'v{self.get_latest_api_version()}'
108
+
105
109
  # Import inner object classes so their methods can be called from the primary object
106
110
  self.chatter = self._import_chatter_class()
107
111
  self.knowledge = self._import_knowledge_class()
@@ -271,12 +275,33 @@ class Salesforce(object):
271
275
  headers=headers, timeout=timeout, show_full_error=show_full_error,
272
276
  return_json=return_json)
273
277
 
274
- def get_api_versions(self):
278
+ def get_api_versions(self) -> list:
275
279
  """This method returns the API versions for the Salesforce releases.
276
280
  (`Reference <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_versions.htm>`_)
281
+
282
+ :returns: A list containing the API metadata from the ``/services/data`` endpoint.
277
283
  """
278
284
  return self.get('/services/data')
279
285
 
286
+ def get_latest_api_version(self) -> str:
287
+ """This method returns the latest Salesforce API version by querying the authorized org.
288
+
289
+ .. version-added:: 1.4.0
290
+
291
+ :returns: The latest Salesforce API version for the authorized org as a string (e.g. ``65.0``)
292
+ """
293
+ versions = self.get_api_versions()
294
+ try:
295
+ latest_version = versions[-1]['version']
296
+ except Exception as exc:
297
+ exc_type = type(exc).__name__
298
+ logger.warning(
299
+ f"Failed to retrieve API version due to a(n) {exc_type} exception; defaulting to "
300
+ f"the fallback version {FALLBACK_SFDC_API_VERSION}"
301
+ )
302
+ latest_version = FALLBACK_SFDC_API_VERSION
303
+ return latest_version
304
+
280
305
  def get_org_limits(self):
281
306
  """This method returns a list of all org limits.
282
307
 
@@ -357,7 +382,6 @@ class Salesforce(object):
357
382
  query = core_utils.url_encode(query)
358
383
  return self.get(f'/services/data/{self.version}/search/?q={query}')
359
384
 
360
-
361
385
  def create_sobject_record(self, sobject, payload):
362
386
  """This method creates a new record for a specific sObject.
363
387
  (`Reference <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_create.htm>`_)
@@ -6,7 +6,7 @@
6
6
  :Example: ``logger = log_utils.initialize_logging(__name__)``
7
7
  :Created By: Jeff Shurtliff
8
8
  :Last Modified: Jeff Shurtliff
9
- :Modified Date: 22 Jan 2023
9
+ :Modified Date: 31 Dec 2025
10
10
  """
11
11
 
12
12
  import os
@@ -59,7 +59,7 @@ class LessThanFilter(logging.Filter):
59
59
  self.max_level = exclusive_maximum
60
60
 
61
61
  def filter(self, record):
62
- """This method returns a Boolean integer value indicating whether or not a message should be logged.
62
+ """This method returns a Boolean integer value indicating whether a message should be logged.
63
63
 
64
64
  .. note:: A non-zero return indicates that the message will be logged.
65
65
  """
@@ -69,6 +69,9 @@ class LessThanFilter(logging.Filter):
69
69
  def _apply_defaults(_logger_name, _formatter, _debug, _log_level, _file_level, _console_level, _syslog_level):
70
70
  """This function applies default values to the configuration settings if not explicitly defined.
71
71
 
72
+ .. version-changed:: 1.4.0
73
+ The default logging level is now defined.
74
+
72
75
  :param _logger_name: The name of the logger instance
73
76
  :type _logger_name: str, None
74
77
  :param _formatter: The log format to utilize for the logger instance
@@ -79,8 +82,9 @@ def _apply_defaults(_logger_name, _formatter, _debug, _log_level, _file_level, _
79
82
  :type _log_level: str, None
80
83
  :returns: The values that will be used for the configuration settings
81
84
  """
85
+ _default_log_level = LOGGING_DEFAULTS.get('log_level')
82
86
  _log_levels = {
83
- 'general': _log_level,
87
+ 'general': _log_level or _default_log_level,
84
88
  'file': _file_level,
85
89
  'console': _console_level,
86
90
  'syslog': _syslog_level,
@@ -90,12 +94,9 @@ def _apply_defaults(_logger_name, _formatter, _debug, _log_level, _file_level, _
90
94
  for _log_type in _log_levels:
91
95
  _log_levels[_log_type] = 'debug'
92
96
  else:
93
- if _log_level:
94
- for _lvl_type, _lvl_value in _log_levels.items():
95
- if _lvl_type != 'general' and _lvl_value is None:
96
- _log_levels[_lvl_type] = _log_level
97
- else:
98
- _log_level = LOGGING_DEFAULTS.get('log_level')
97
+ for _lvl_type, _lvl_value in _log_levels.items():
98
+ if _lvl_value is None:
99
+ _log_levels[_lvl_type] = _log_levels['general']
99
100
  if _formatter and isinstance(_formatter, str):
100
101
  _formatter = logging.Formatter(_formatter)
101
102
  _formatter = LOGGING_DEFAULTS.get('formatter') if not _formatter else _formatter
@@ -0,0 +1,202 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ :Module: salespyforce.utils.tests.conftest
4
+ :Synopsis: Configuration for performing unit testing with pytest
5
+ :Usage: Leveraged by pytest in test modules
6
+ :Example: ``soql_response = salesforce_unit.soql_query(soql_statement)``
7
+ :Created By: Jeff Shurtliff
8
+ :Last Modified: Jeff Shurtliff
9
+ :Modified Date: 13 Dec 2025
10
+
11
+ Pytest fixtures for ``salespyforce.utils.tests``.
12
+
13
+ This module centralizes helpers used across the test suite to avoid
14
+ repeated setup in individual test files. It introduces two key fixtures:
15
+
16
+ * ``salesforce_integration`` — Instantiates the real ``Salesforce``
17
+ client when a helper file is available. Tests using this fixture are
18
+ marked as ``integration`` and are skipped unless ``--integration`` is
19
+ provided.
20
+ * ``salesforce_unit`` — Provides a lightweight stub that mimics the
21
+ public API used by existing tests without performing any network I/O.
22
+
23
+ The goal is to make it easy to switch between fast, isolated unit tests
24
+ and opt-in integration runs against a real Salesforce org. Additional
25
+ fixtures can be added here to share common mocking or configuration.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import os
31
+ from pathlib import Path
32
+ from types import SimpleNamespace
33
+ from typing import Iterator
34
+
35
+ import pytest
36
+
37
+ from salespyforce.core import Salesforce
38
+
39
+ # Define constants
40
+ HELPER_FILE_NAME = "helper_dm_conn.yml"
41
+
42
+
43
+ # -----------------------------
44
+ # Pytest configuration hooks
45
+ # -----------------------------
46
+
47
+ def pytest_addoption(parser: pytest.Parser) -> None:
48
+ """This function registers custom CLI options.
49
+
50
+ .. version-added:: 1.4.0
51
+
52
+ ``--integration`` enables tests that require access to a real
53
+ Salesforce org. Keeping this opt-in protects routine runs from
54
+ network or credential dependencies.
55
+ """
56
+ parser.addoption(
57
+ "--integration",
58
+ action="store_true",
59
+ default=False,
60
+ help="run tests that require a Salesforce helper file",
61
+ )
62
+
63
+
64
+ def pytest_configure(config: pytest.Config) -> None:
65
+ """This function declares custom markers so pytest will not warn during collection.
66
+
67
+ .. version-added:: 1.4.0
68
+ """
69
+ config.addinivalue_line(
70
+ "markers",
71
+ "integration: marks tests that require a real Salesforce org",
72
+ )
73
+
74
+
75
+ def pytest_collection_modifyitems(
76
+ config: pytest.Config, items: list[pytest.Item]
77
+ ) -> None:
78
+ """This function skips integration tests when ``--integration`` is not provided.
79
+
80
+ .. version-added:: 1.4.0
81
+ """
82
+ if config.getoption("--integration"):
83
+ return
84
+
85
+ skip_integration = pytest.mark.skip(
86
+ reason="requires --integration to run against a Salesforce org"
87
+ )
88
+ for item in items:
89
+ if "integration" in item.keywords:
90
+ item.add_marker(skip_integration)
91
+
92
+
93
+ # -----------------------------
94
+ # Helper utilities
95
+ # -----------------------------
96
+
97
+ def _find_helper_file() -> Path | None:
98
+ """This function locates a helper file in common locations used by this project.
99
+
100
+ .. version-added:: 1.4.0
101
+ """
102
+ helper_locations = [
103
+ Path(os.environ.get("HOME", "")) / "secrets" / HELPER_FILE_NAME,
104
+ Path("local") / HELPER_FILE_NAME,
105
+ ]
106
+ for helper_path in helper_locations:
107
+ if helper_path.is_file():
108
+ return helper_path.resolve()
109
+ return None
110
+
111
+
112
+ # -----------------------------
113
+ # Fixtures
114
+ # -----------------------------
115
+
116
+ @pytest.fixture(scope="session")
117
+ def integration_helper_file() -> Path:
118
+ """This fixture returns the helper file path or skips the test if none is available.
119
+
120
+ .. version-added:: 1.4.0
121
+
122
+ Keeping this lookup in a session-scoped fixture ensures we only
123
+ perform filesystem checks once per test run and provides a single
124
+ source of truth for integration tests.
125
+ """
126
+ helper_path = _find_helper_file()
127
+ if helper_path is None:
128
+ pytest.skip("No Salesforce helper file found for integration tests")
129
+ return helper_path
130
+
131
+
132
+ @pytest.fixture(scope="session")
133
+ def salesforce_integration(integration_helper_file: Path) -> Iterator[Salesforce]:
134
+ """This fixture instantiates the real Salesforce client for integration tests.
135
+
136
+ .. version-added:: 1.4.0
137
+
138
+ The fixture is session-scoped to avoid repeated authentication and
139
+ to reuse connections across tests. It is intended only for tests
140
+ marked with ``@pytest.mark.integration``.
141
+ """
142
+ client = Salesforce(helper=str(integration_helper_file))
143
+ yield client
144
+
145
+
146
+ @pytest.fixture()
147
+ def salesforce_unit(monkeypatch: pytest.MonkeyPatch) -> SimpleNamespace:
148
+ """This fixture provides a lightweight stub that mimics the ``Salesforce`` API.
149
+
150
+ .. version-added:: 1.4.0
151
+
152
+ This fixture avoids network calls by supplying deterministic return
153
+ values for the subset of methods exercised by the current tests.
154
+ It can be extended as coverage grows to keep unit tests fast and
155
+ self-contained.
156
+ """
157
+ # Minimal data used across tests
158
+ sample_urls = {
159
+ "base_url": "https://example.force.com",
160
+ "rest_resources": {"metadata": "available"},
161
+ "org_limits": {"DailyApiRequests": {"Remaining": 15000}},
162
+ "sobjects": {"sobjects": []},
163
+ "account": {
164
+ "objectDescribe": {},
165
+ "activateable": False,
166
+ },
167
+ "soql_result": {
168
+ "done": True,
169
+ "totalSize": 1,
170
+ "records": [{"Id": "001XX000003NGqqYAG"}],
171
+ },
172
+ "sosl_result": {
173
+ "searchRecords": [
174
+ {"attributes": {"type": "Account"}, "Id": "001XX000003NGqqYAG"}
175
+ ]
176
+ },
177
+ }
178
+
179
+ def _create_response(**overrides):
180
+ """This function creates an API response payload mimicking the Salesforce REST API responses.
181
+
182
+ .. version-added:: 1.4.0
183
+ """
184
+ response = {"id": "001D000000IqhSLIAZ", "success": True, "errors": []}
185
+ response.update(overrides)
186
+ return response
187
+
188
+ stub = SimpleNamespace()
189
+ stub.base_url = sample_urls["base_url"]
190
+ stub.get_api_versions = lambda: [{"version": "v65.0"}]
191
+ stub.get_rest_resources = lambda: sample_urls["rest_resources"]
192
+ stub.get_org_limits = lambda: sample_urls["org_limits"]
193
+ stub.get_all_sobjects = lambda: sample_urls["sobjects"]
194
+ stub.get_sobject = lambda *_args, **_kwargs: sample_urls["account"]
195
+ stub.describe_object = lambda *_args, **_kwargs: sample_urls["account"]
196
+ stub.create_sobject_record = (
197
+ lambda *_args, **_kwargs: _create_response()
198
+ )
199
+ stub.soql_query = lambda *_args, **_kwargs: sample_urls["soql_result"]
200
+ stub.search_string = lambda *_args, **_kwargs: sample_urls["sosl_result"]
201
+
202
+ return stub
@@ -6,7 +6,7 @@
6
6
  :Example: ``exceptions = resources.import_exceptions_module()``
7
7
  :Created By: Jeff Shurtliff
8
8
  :Last Modified: Jeff Shurtliff
9
- :Modified Date: 27 May 2023
9
+ :Modified Date: 14 Nov 2025
10
10
  """
11
11
 
12
12
  import os
@@ -17,7 +17,7 @@ import pytest
17
17
 
18
18
  # Define constants
19
19
  SKIP_LOCAL_TEST_MSG = 'skipping local-only tests'
20
- HELPER_FILE_NAME = 'helper_shurt.yml'
20
+ HELPER_FILE_NAME = 'helper_dm_conn.yml'
21
21
 
22
22
 
23
23
  class MockResponse: